@

树套树

将两个树形的数据结构进行嵌套的数据结构

线段树套平衡树

https://www.luogu.org/problem/P3380

首先在外层建一棵线段树。

线段树的每一个节点都为\([l,r]\)区间的信息

此时在\([l,r]\)这个节点上建一棵平衡树

然后这种树套树支持的操作:

1)查询z值在\([l,r]\)区间的排名

就类似于普通的线段树查询,将\([l,r]\)拆成\(log(n)\)个区间,然后把对于所有的这些区间在treap里面可以找到有多少个比它小的数

2) 查询\([l,r]\)区间上的值

二分然后使用(1)的函数在check

3)修改某个值

对于所有包含这个数的区间做修改即可。

4)\([l,r]\)寻找前驱/后继

也是拆成\(log(n)\)个区间,然后去查询对于所有这些区间的前驱或后继里面最大/小的那个。

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ls1 k<<1
#define rs1 k<<1|1
#define ls2 x[k].s[0]
#define rs2 x[k].s[1]
using namespace std;
const int N=5e4+5,inf=2147483647;
struct X{
	int z1,z2,s[2],sz,sl;
	void xg(int z){
		z1=z,z2=rand();
		sz=sl=1;
	}
}x[N*40];
int ans,cz[N],n,m,s;
struct Treap{
	int rt;
	void pu(int k){
		x[k].sz=x[ls2].sz+x[k].sl+x[rs2].sz;
	}
	void xz(int& k,int p){
		int t=x[k].s[p];
		x[k].s[p]=x[t].s[p^1];
		x[t].s[p^1]=k;
		x[t].sz=x[k].sz;
		pu(k),k=t;
	}
	void updt(int& k,int z){
		if(!k) x[k=++s].xg(z);
		else{
			++x[k].sz;
			if(x[k].z1==z) ++x[k].sl;
			else{
				int p=z>x[k].z1;
				updt(x[k].s[p],z);
				if(x[x[k].s[p]].z2<x[k].z2) xz(k,p);
			}
		}
	}
	void del(int& k,int z){
		if(x[k].z1==z){
			if(x[k].sl>1) --x[k].sl;
			else if(ls2&&rs2){
				int p=x[rs2].z2<x[ls2].z2;
				xz(k,p),del(x[k].s[p^1],z); 
			}
			else k=ls2+rs2;
		}
		else del(x[k].s[x[k].z1<z],z);
		pu(k);
	}
	int qry1(int k,int z){
		if(!k) return 0;
		if(z==x[k].z1) return x[ls2].sz;
		else if(z<x[k].z1) return qry1(ls2,z);
		else return x[ls2].sz+x[k].sl+qry1(rs2,z);
	}
	void qry4(int k,int z){
		if(!k) return;
		if(z<=x[k].z1) qry4(ls2,z);
		else ans=max(ans,x[k].z1),qry4(rs2,z);
	}
	void qry5(int k,int z){
		if(!k) return;
		if(z>=x[k].z1) qry5(rs2,z);
		else ans=min(ans,x[k].z1),qry5(ls2,z);
	}
}tr[N<<2];
void bt(int k,int l,int r){
	for(int i=l;i<=r;++i) tr[k].updt(tr[k].rt,cz[i]);
	if(l!=r){
		int mid=(l+r)>>1;
		bt(ls1,l,mid),bt(rs1,mid+1,r);
	}
}
void updt(int k,int l,int r,int w,int z){
	tr[k].del(tr[k].rt,cz[w]);
	tr[k].updt(tr[k].rt,z);
	if(l!=r){
		int mid=(l+r)>>1;
		w<=mid?updt(ls1,l,mid,w,z):updt(rs1,mid+1,r,w,z);
	}
}
int qry1(int k,int l,int r,int ql,int qr,int z){
	if(ql<=l&&r<=qr) return tr[k].qry1(tr[k].rt,z);
	else{
		int mid=(l+r)>>1,re=0;
		if(ql<=mid) re+=qry1(ls1,l,mid,ql,qr,z);
		if(qr>mid) re+=qry1(rs1,mid+1,r,ql,qr,z);
		return re;
	}
}
int qry2(int ql,int qr,int z){
	int l=-1,r=1e8+1;
	while(r-1>l){
		int mid=(l+r)>>1;
		if(qry1(1,1,n,ql,qr,mid)+1<=z) l=mid;
		else r=mid;
	}
	return l;
}
void qry4(int k,int l,int r,int ql,int qr,int z){
	if(ql<=l&&r<=qr) tr[k].qry4(tr[k].rt,z);
	else{
		int mid=(l+r)>>1;
		if(ql<=mid) qry4(ls1,l,mid,ql,qr,z);
		if(qr>mid) qry4(rs1,mid+1,r,ql,qr,z);
	}
}
void qry5(int k,int l,int r,int ql,int qr,int z){
	if(ql<=l&&r<=qr) tr[k].qry5(tr[k].rt,z);
	else{
		int mid=(l+r)>>1;
		if(ql<=mid) qry5(ls1,l,mid,ql,qr,z);
		if(qr>mid) qry5(rs1,mid+1,r,ql,qr,z);
	}
}
int main(){
	freopen("a.in","r",stdin);
	scanf("%d%d",&n,&m);
	srand(n);
	for(int i=1;i<=n;++i) scanf("%d",&cz[i]);
	bt(1,1,n);
	while(m--){
		int opt,l,r,k;
		scanf("%d",&opt);
		if(opt==3){
			scanf("%d%d",&l,&k);
			updt(1,1,n,l,k);
			cz[l]=k;
		}
		else{
			scanf("%d%d%d",&l,&r,&k);
			if(opt==1) ans=qry1(1,1,n,l,r,k)+1;
			else if(opt==2) ans=qry2(l,r,k);
			else if(opt==4) ans=-inf,qry4(1,1,n,l,r,k);
			else ans=inf,qry5(1,1,n,l,r,k);
			printf("%d\n",ans);
		}
	}
	return 0;
} 

树状数组套线段树

同样的问题,也可以这样的树套树解决。

外层树状数组维护区间,内部线段树维护权值(首先要输入所有值和查询修改操作再离散化)。

具体来说树状数组tr [ i ]这个点上

保存了[ i - lowbit(i) +1 , i ]这个区间的一个值域的线段树

然后每次查询 [ l , r ] 上 [ x , y ]的信息时,

就要先设一个数组,记录 [1 , l-1 ] 和[ 1, r ]根据拆分之后的的几个区间的根节点的位置

1)查询z值在[l,r]区间的排名

我们首先如上文所说挑出那些节点之后,

就查询这些节点对应的线段树里面小于z的值有多少个即可

2)查询排名为x在\([l,r]\)中的数是哪个

每次统计左儿子的权值和,就可以知道这个数是在左区间还是右区间。

然后再不断递归即可

3)修改

这个操作类似与一开始加入节点的操作,其实就是在包含这个数的树状数组中进行-1+1的修改即可

4)找前驱

用1)找到它的排名,然后用2)找到\([l,r]\)找名次为它的名次减1的数就可以了。

5)找后继同理

#include<cstdio>
#include<algorithm>
#define ls x[k].s[0]
#define rs x[k].s[1]
using namespace std;
const int N=5e4+5,inf=2147483647;
struct X{
	int s[2],z;
}x[N*100];
int a[N],val[N<<1],tn,n,m;
int rt[N],cnt;
int s1,q1[25],s2,q2[25];
void updt(int& k,int l,int r,int w,int z){
	if(!k) k=++cnt;
	x[k].z+=z;
	if(l!=r) 
	{
		int mid=(l+r)>>1;
		w<=mid?updt(ls,l,mid,w,z):updt(rs,mid+1,r,w,z);
	}
}
void add(int i,int z){
	int w=lower_bound(val+1,val+tn+1,a[i])-val;
	for(;i<=n;i+=i&-i) updt(rt[i],1,tn,w,z);
}
void go_son(int p){
	for(int i=1;i<=s1;++i) q1[i]=x[q1[i]].s[p];
	for(int i=1;i<=s2;++i) q2[i]=x[q2[i]].s[p];
}
int calc(){
	int re=0;
	for(int i=1;i<=s1;++i) re-=x[x[q1[i]].s[0]].z;
	for(int i=1;i<=s2;++i) re+=x[x[q2[i]].s[0]].z;
	return re;
}
int qry1(int l,int r,int z){
	if(l==r) return 0;
	int mid=(l+r)>>1,tz;
	if(z<=mid){
		go_son(0);
		return qry1(l,mid,z);
	}
	tz=calc();
	go_son(1);
	return tz+qry1(mid+1,r,z); 
}
int qry2(int l,int r,int z){
	if(l==r) return val[l];
	int mid=(l+r)>>1,tz=calc();
	if(z<=tz){
		go_son(0);
		return qry2(l,mid,z);
	}
	go_son(1);
	return qry2(mid+1,r,z-tz);
}
struct Q{
	int opt,l,r,k;
	void in(){
		scanf("%d%d%d",&opt,&l,&r);
		if(opt==3) val[++tn]=r;
		else{
			scanf("%d",&k);
			if(opt!=2) val[++tn]=k;
		}
	}
	void init(){
		s1=s2=0;
		for(int i=l-1;i;i-=i&-i) q1[++s1]=rt[i];
		for(int i=r;i;i-=i&-i) q2[++s2]=rt[i];
	}
	void cl(){
		int ans;
		if(opt==3){
			add(l,-1);
			a[l]=r;
			add(l,1);
		}
		else{
			init();
			if(opt!=2) k=lower_bound(val+1,val+tn+1,k)-val;
			if(opt==1) ans=qry1(1,tn,k)+1;
			else if(opt==2) ans=qry2(1,tn,k);
			else if(opt==4){
				int rk=qry1(1,tn,k);
				init();
				if(!rk) ans=-inf;
				else ans=qry2(1,tn,rk);
			}
			else{
				int rk=qry1(1,tn,k+1);
				init();
				if(rk>r-l) ans=inf;
				else ans=qry2(1,tn,rk+1);
			}
			printf("%d\n",ans);
	
		}
	}
}q[N];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		val[++tn]=a[i];
	}
	for(int i=1;i<=m;++i) q[i].in();
	sort(val+1,val+tn+1);
	tn=unique(val+1,val+tn+1)-val-1;
	for(int i=1;i<=n;++i) add(i,1);
	for(int i=1;i<=m;++i) q[i].cl();
	return 0;
}