线段树合并、分裂

基本概念:

如果需要维护许多个大小为 \(10^5\) 级别的多重集,可以看做给每一个多重集建立一棵线段树。线段树的合并、分裂就是多重集的累加、分开。

这里使用动态开点的方式存储线段树树。

如果一个节点为空,那么它的编号为 \(0\)

变量释义:

  • \(cnt\) 个多重集

  • 建立了 \(tot\) 个节点

  • 若一个多重集的编号为 \(x\) ,它的根节点编号为 \(root[x]\)

注意:空间是个谜!能开多大是多大

update:经过 ZCETHAN 的严格证明,空间大小开到 \(O(n\times (\left\lceil \log n\right\rceil+1))\) 足矣。

线段树合并

把以 \(y\) 为根的线段树合并到以 \(x\) 为根的线段树:

int merge(int x,int y,int nl,int nr) // i:y->x
{
	 if(!x || !y) return x+y;
	 int mid=(nl+nr)>>1;
	 //tree[x].sum+=tree[y].sum; 根据题目改动
	 tree[x].ls=merge(tree[x].ls,tree[y].ls,nl,mid);
	 tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,nr);
	 //pushup(x);
	 del(y);
	 return x;
}

复杂度 \(=\) 节点数 ( 一般均摊下来可以达到一次操作 \(O(\log n)\) 的级别 )

线段树分裂

把以 \(x\) 为根的线段树中 \(\ge k\) 的数转移到一棵 空的 线段树 \(y\)

void split(int &x,int &y,int nl,int nr,int k) // i>=k i:x->y
{
	 if(!x) x=++tot;
	 if(!y) y=++tot;
	 if(nl==nr) { swap(x,y); return; }
	 int mid=(nl+nr)>>1;
	 if(mid>=k)
	 {
	 	 swap(tree[x].rs,tree[y].rs);
	 	 split(tree[x].ls,tree[y].ls,nl,mid,k);
	 }
	 else split(tree[x].rs,tree[y].rs,mid+1,nr,k);
	 pushup(x),pushup(y);
}

update:线段树合并复杂度证明

我们知道对于 \(n\) 个点的启发式合并时 \(\mathcal{O(n\log n)}\) 的。

在一次合并的过程中,我们需要访问的点必定是两棵树重合的部分,这些点的数量一定小于两棵树中较小的那个。

那么就相当于将小的树合并到大的树中去,说明线段树合并属于启发式合并的一种,证明它的复杂度为 \(n\log n\)

推广:类似 Trie 树合并等的复杂度也可以用上面的方法证明是 \(n\log n\) 的。

例题:

P5494 【模板】线段树分裂 \(\rightarrow\) 模板代码

偷袭来的题单

posted @ 2020-09-26 11:14  EricQian06  阅读(130)  评论(0编辑  收藏  举报