树套树

线段树套平衡树

通常平衡树用splay/FHQ treap。

树套树的构建:外层的线段树正常构建,内层的平衡树在每个节点上构建。


插入and建树

每层插入n个元素,一共\(\log n\)层,空间复杂度\((n+m)\log n\),时间复杂度\(O(n\log ^2 n)\)

const int N=5e4+10;
int root[N<<2],w[N];//w为要维护的序列

il void Insert(int &root,int v){
	int x=root,fa=0;
	while(x){fa=x,x=t[x].size[v>t[x].val]};
	x=++tot;
	t[fa].s[v>t[fa].val]=x;
	t[x].init(fa,v);
	splay(root,x,0);
}

void build(int k,int l,int r){//k代表根节点,l r用来分裂,在每个节点上建立相应的平衡树 
	Insert(root[k],-inf);Insert(root[u],inf);
	rep(i,l,r) Insert(root[u],w[i]); 
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}

求某区间中值的排名

线段树负责裂开,平衡树负责查找。

把线段树不断裂开,遇到已覆盖的区间,在该区间平衡树中查找比该值小的元素个数,区间结果合并时,将小的元素个数求和。

线段树走\(\log n\) 层,平衡树查\(\log n\)层,每次时间复杂度\(\log ^2 n\)

int Get_rk(int k,int v){
	int x=k,res=0;
	while(x){
		if(t[x].val<v){
			res+=t[x<<1].size+1;
			x=x<<1|1;
		}
		else x=x<<1;
	}
	return res;
}

int Query_rk(int k,int l,int r,int fl,int fr,int v){//最终输出答案时要加1 因为这是查询的比v小的个数 
	if(fl<=l&&fr>=r) return Get_rank(root[k],v)-1;//走进平衡树查找,-1是因为有哨兵的存在 
	int mid=(l+r)>>1,res=0;
	if(fl<=mid)
		res+=Query_rk(k<<1,l,mid,fl,fr,v);
	if(fr>mid)
		res+=Query_rk(k<<1|1,mid+1,r,fl,fr,v);
	return res;
}

求排名为k的数值

分裂之后很难拼出k,所以二分答案求排名。用小于该值的元素个数作为答案进行二分,即可得答案。

时间复杂度\(O(\log ^3 n)\)

int Query_val(int k,int fl,int fr,int v){//查询排名为k的数值 
	int l=0,r=1e8,ans;
	while(l<=r) {//二分答案 
		int mid=(l+r)>>1;
		if(Query_rk(1,1,n,fl,fr,mid)+1<=v)//如果查找到的该点排名比所求要小,l=mid+1,ans=mid
			l=mid+1,ans=mid;
		else r=mid-1;//否则缩小右区间 
	}
	return ans;	
}

单点修改(及删除操作

沿着线段树的路径走,穿透每一层,在所有包含该数的平衡树中先删除该数,再插入另一个数。

il void del(int &root,int v){//别忘了将维护的序列进行修改 
	int x=root;
	while(x){//找到该值 
		if(t[x].val==v) break;
		if(t[x].val<v) x=x<<1|1;
		else x=x<<1;
	}
	splay(root,x,0);
	int l=x<<1,r=x<<1|1;//前驱后继 
	while(l<<1|1) l=l<<1|1;//找左子树的最大值
	while(r<<1) r=r<<1;//找右子树的最小值
	splay(root,l,0);//前驱转为根节点 
	splay(root,r,l);//后继转为根节点的右子树 即可删除该点
	r<<1=0;//删除 
	splay(root,r,0);
}

void change(int k,int l,int r,int pos,int v){//根节点,区间,坐标,修改后的数值 
	del(root[k],w[pos]);
	Insert(root[k],v);
	if(l==r) return ;
	int mid=(l+r)>>1;
	if(pos<=mid)
		change(k<<1,l,mid,pos,v);
	else change(k<<1|1,mid+1,r,pos,v);
}

求某区间中某值的前驱

线段树负责裂开,平衡树负责查找。

遇到已覆盖的区间,在该区间平衡树中查找该值的前驱,结果合并时取最大值。

线段树走\(\log n\)层,平衡树查\(\log n\)层,时间复杂度为\(O(\log ^2 n)\)

int Get_pre(int k,int v){//在平衡树中找前驱 
	int x=k,res=-inf;
	while(x){//找最值 
		if(t[x].val<v) res=t[x].val,x=x<<1|1;//往左或右找 
		else x=x<<1;
	}
	return res;
}

int Query_pre(int k,int l,int r, int fl,int fr,int v){//在覆盖区间内找最值 
	if(fl<=l&&r<=fr) return Get_pre(root[k],v);
	int mid=(l+r)>>1,res=-inf;
	if(fl<=mid)
		res=Max(res,Query_pre(k<<1,l,mid,fl,fr,v));
	if(fr>mid)
		res=Max(res,Query_pre(k<<1|1,mid+1,r,fl,fr,v));
	return res;
}

求某区间中某值的后继

类似求前驱。最后结果合并时取最小值。

int Get_suc(int k,int v){//在平衡树中找后继 
	int x=k,res=inf;
	while(x){//找最值 
		if(t[x].val>v) res=t[x].val,x=x<<1;//往左或右找 
		else x=x<<1|1;
	}
	return res;
}

int Query_suc(int k,int l,int r, int fl,int fr,int v){//在覆盖区间内找最值 
	if(fl<=l&&r<=fr) return Get_suc(root[k],v);
	int mid=(l+r)>>1,res=inf;
	if(fl<=mid)
		res=Min(res,Query_suc(k<<1,l,mid,fl,fr,v));
	if(fr>mid)
		res=Min(res,Query_suc(k<<1|1,mid+1,r,fl,fr,v));
	return res;
}

线段树套线段树

顾名思义,就是在原有的线段树的每个点上再开一个线段树,也叫二维线段树,用来解决二维动态问题。

空间复杂度和时间复杂度都为\(O(\log ^2 n)\),因为外层线段树上进行\(\log n\)次,再在内层线段树上进行\(log n\)次,合起来就是\(O(\log^2n)\)

面对多维度信息的题目时,如果题目没有要求强制在线,我们还可以考虑** CDQ 分治** ,或者 整体二分 等分治算法,来避免使用高级数据结构,减少代码实现难度。

例题

陌上花开

题意:有\(n\)个元素,第\(i\)个元素有属性\(a_i\)\(b_i\)\(c_i\),设\(f(i)\)表示满足\(a_j\leq a_i\)\(b_j\leq b_i\)\(c_j\leq c_i\)\(i\not = j\)\(j\)的数量。

对于$d \in [0,n) \(,求\)f(i)=d$的数量。

将第一维排序处理,然后用树套树维护第二第三维。---oi wiki

具体来讲,我们将第二维和第三维视为平面中的一点的坐标,然后每次单点修改,二维区间查询,就是统计一个矩形内部的元素之和。

我们对第一棵线段树的每一个结点进行修改是实际上就是对这个结点中套着的线段树进行修改,对套着的线段树进行修改是才是和一维线段树一样的修改方法。

其实我也没很理解,结合代码稍微体会一下吧 ( ?。

const int N=2e7+10;

int n,B,tot1,tot2,root1,root[N],ans[N],d[N],lst;
struct node{
	int a,b,c;
}g[N];

struct Node{
	int l,r;
}t1[N],t2[N];

int cmp(node x,node y){
	return x.a<y.a;
}

void Modify_2(int &k,int l,int r,int i){
	if(!k) k=++tot2;
	d[k]+=1;
	if(l==r) return ;
	int mid=(l+r)>>1;
	if(mid>=g[i].c) Modify_2(t2[k].l,l,mid,i);
	else Modify_2(t2[k].r,mid+1,r,i);
}

void Modify_1(int &k,int l,int r,int i){
	if(!k) k=++tot1;
	Modify_2(root[k],1,B,i);
	if(l==r) return ;
	int mid=(l+r)>>1;
	if(mid>=g[i].b) Modify_1(t1[k].l,l,mid,i);
	else Modify_1(t1[k].r,mid+1,r,i);
}

int Query_2(int k,int l,int r,int fl,int fr,int i){
	if(!k) return 0;
	if(fl<=l&&fr>=r) return d[k];
	int mid=(l+r)>>1,res=0;
	if(fl<=mid)
		res+=Query_2(t2[k].l,l,mid,fl,fr,i);
	if(mid+1<=fr)
		res+=Query_2(t2[k].r,mid+1,r,fl,fr,i);
	return res;
}

int Query_1(int k,int l,int r,int fl,int fr,int i){
	if(!k) return 0;
	if(fl<=l&&r<=fr) return Query_2(root[k],1,B,1,g[i].c,i);
	int mid=(l+r)>>1,res=0;
	if(fl<=mid)
		res+=Query_1(t1[k].l,l,mid,fl,fr,i);
	if(mid+1<=fr)
		res+=Query_1(t1[k].r,mid+1,r,fl,fr,i);
	return res;
}

signed main(){
	n=read();B=read();
	rep(i,1,n){
		g[i].a=read();
		g[i].b=read();
		g[i].c=read();
	}
	sort(g+1,g+1+n,cmp);lst=1;
	rep(i,1,n){
		Modify_1(root1,1,B,i);
		if(g[i+1].a!=g[i].a){
			rep(j,lst,i)
				ans[Query_1(root1,1,B,1,g[j].b,j)-1]++;
			lst=i+1;
		}
	}
	rep(i,0,n-1)
	cout<<ans[i]<<'\n';
	return 0;
}

Census

posted @ 2023-06-19 19:38  XYini  阅读(3)  评论(0)    收藏  举报