• 树套树


    @

    树套树

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

    线段树套平衡树

    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
    ",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
    ",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;
    }
    
  • 相关阅读:
    java:transient是什么,有什么作用
    如何阅读java源码
    java里面list是引用的好例子
    sort给文件按照大小排序
    HBase的rowkey排序和scan输出顺序
    记录一次事故——idea,sbt,scala
    一个简单的synchronized多线程问题、梳理与思考
    Android TextView文字描边的实现!!
    android中include标签的使用
    layout_weight 的解释及使用
  • 原文地址:https://www.cnblogs.com/bzmd/p/14198049.html
Copyright © 2020-2023  润新知