description
你需要维护nnn个可重整数集,开始都是空集
现在有mmm次操作
1 l r k在[l,r][l,r][l,r]的集合中加入一个数kkk2 l r k询问[l,r][l,r][l,r]的集合的并集中的第kkk大,特别的,并集不去重
solution
看到动态区间第kkk大,显然想到树套树
但是树状数组套权值线段树或者线段树套权值线段树都不能支持在外围的区间修改
所以我们可以考虑反过来
用权值树状数组套线段树
外围的树状数组表示每一个数值,里面的线段树表示这个数值在每个位置上出现的次数
那么这样的话
对于每一个opt=1opt=1opt=1,转化成了一个点的区间修改,复杂度O(nlog2n)O(n\log^2n)O(nlog2n)
对于每一个opt=2opt=2opt=2,我们先二分,然后判断是否可行,就可以得到答案,复杂度O(nlog3n)O(n\log^3 n)O(nlog3n)
空间复杂度的话,每次最多修改logn\log nlogn棵线段树,每棵线段树最多会多出logn\log nlogn个节点(注意线段树区间修改的修改点数也是log级别的),所以空间复杂度是O(nlog2n)O(n\log ^2 n)O(nlog2n)
虽然n≤5×104n\leq 5\times 10^4n≤5×104,但是这道题的值域是2n2n2n级别的,所以3个log\loglog的做法实际只能通过34分
我们考虑如何进行优化
我们为什么要选用权值树状数组而不是线段树呢,是因为他的常数更小
但是我们发现,在线段树上进行二分是2log的,而树状数组二分通常是3log的(虽然有2log做法但是我不会qwq),所以我们可以在外围的权值树状数组变成权值线段树
于是这题可能成为了权值线段树套线段树的唯一模板题…
code
码量也不是很大,树套树这个东西看着挺恶心其实通常还不是很难写
#include <bits/stdc++.h>
using namespace std;
# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)
typedef long long ll;
const int N=1e5+5;
template<typename T> void read(T &x){
x=0;int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
x*=f;
}
int n,m;
int root[N<<2];
int tot;
struct node{
int lc,rc;
ll val,tag;
}seg[N*200];
int lowbit(int o){
return o&-o;
}
void update(int &u,int l,int r,int ql,int qr,int k){
if(!u)u=++tot;
seg[u].val+=1ll*(min(r,qr)-max(l,ql)+1)*k;
if(l>=ql&&r<=qr){
seg[u].tag+=k;
return;
}
int mid=l+r>>1;
if(ql<=mid)update(seg[u].lc,l,mid,ql,qr,k);
if(qr>mid)update(seg[u].rc,mid+1,r,ql,qr,k);
}
void add(int u,int l,int r,int x,int ql,int qr){
update(root[u],1,n,ql,qr,1);
if(l==r)return;
int mid=l+r>>1;
if(x<=mid)add(u<<1,l,mid,x,ql,qr);
else add(u<<1|1,mid+1,r,x,ql,qr);
}
ll query(int u,int l,int r,int ql,int qr,int tagsum){
if(l>=ql&&r<=qr)return seg[u].val+1ll*(r-l+1)*tagsum;
int mid=l+r>>1;
ll res=0;
if(ql<=mid)res+=query(seg[u].lc,l,mid,ql,qr,tagsum+seg[u].tag);
if(qr>mid)res+=query(seg[u].rc,mid+1,r,ql,qr,tagsum+seg[u].tag);
return res;
}
int kth(int u,int l,int r,int ql,int qr,ll k){
if(l==r)return l;
int mid=l+r>>1;
ll rank=query(root[u<<1|1],1,n,ql,qr,0);
if(k<=rank)return kth(u<<1|1,mid+1,r,ql,qr,k);
else return kth(u<<1,l,mid,ql,qr,k-rank);
}
int main()
{
read(n),read(m);
Rep(i,1,m){
int opt,l,r;
ll k;
read(opt),read(l),read(r),read(k);
if(opt==1)add(1,1,2*n+1,k+n+1,l,r);
else printf("%d\n",kth(1,1,2*n+1,l,r,k)-n-1);
}
return 0;
}

本文深入探讨了树套树算法,一种高效解决动态区间第k大问题的方法。通过使用权值线段树套线段树的结构,文章详细解释了算法原理、优化策略及其实现代码,适用于大值域场景。
747

被折叠的 条评论
为什么被折叠?



