1. 程式人生 > 實用技巧 >線段樹合併分裂學習筆記

線段樹合併分裂學習筆記

線段樹合併分裂學習筆記

思想

你想想你寫一顆普通線段樹是怎麼寫的,是不是把子區間的資訊合併到父區間?

線段樹合併大概就是這個想法,在樹上每一個節點維護一顆權值線段樹,把兩棵線段樹的資訊合併到一個線段樹上

線段樹分裂呢,就是把一棵權值線段樹根據排名或值來割裂成兩棵權值線段樹,思路和fhq_treap的分裂類似,如下圖

當然,由於給每一個節點都開一顆靜態線段樹空間肯定不夠,且節點上線段樹可能都不一定滿,所以我們愉快地投進動態開點線段樹的懷抱中.

這兩操作的一次的時間複雜度都是\(O(log_{2}n)\)

線段樹合併空間複雜度及證明

例題

1.P4556 [Vani有約會]雨天的尾巴 /【模板】線段樹合併

這是板子嗎?

對於新增\((x,y)\)路徑上的所有點,暴力肯定是不行的,這裡有一個經典解法是樹上差分,即在\(x\),\(y\)\(+1\),\(lca(x,y)\)\(-1\),\(fa[lca(x,y)]\)\(-1\)(畫畫圖就懂了)

然後我們要處理的問題就是資訊如何從子節點傳上父節點,就線段樹合併完事了.

注意合併時若兩棵樹一棵為空,返回另一棵即可

#include<bits/stdc++.h>
using namespace std;
int const MAXN=1e5+10,MAXM=2e7;
int n,m,tot,tott,_,N;
int h[MAXN],vis[MAXN],siz[MAXN],hson[MAXN],top[MAXN],fa[MAXN],dep[MAXN],val[MAXM],lc[MAXM],rc[MAXM],root[MAXM],ans[MAXM],X[MAXN],Y[MAXN],Z[MAXN],ansn[MAXN];
struct edge{
	int to,next;
}e[MAXN<<1];
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return f*x;
}
inline void add(int u,int v){
	e[++tot].to=v,e[tot].next=h[u],h[u]=tot;
}
void dfs1(int x,int dad){
	siz[x]=1;fa[x]=dad;
	int maxn=-1;
	for(int i=h[x],to;i;i=e[i].next){
		to=e[i].to;
		if(to==dad)continue;
		dep[to]=dep[x]+1;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[to]>maxn)maxn=siz[to],hson[x]=to;
	}
	return;
}
void dfs2(int x,int topf){
	top[x]=topf;
	if(!hson[x])return;
	dfs2(hson[x],topf);
	for(int i=h[x],to;i;i=e[i].next){
		to=e[i].to;
		if(to==fa[x] || to==hson[x])continue;
		dfs2(to,to);
	}
	return;
}
inline int lca(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])return x;
	else return y;
}
void change(int &x,int l,int r,int p,int a){
	if(!x)x=++tott;
	if(l==r){val[x]+=a;if(val[x]>0)ans[x]=p;return;}
	int mid=(l+r)>>1;
	if(p<=mid)change(lc[x],l,mid,p,a);
	if(p>mid)change(rc[x],mid+1,r,p,a);
	if(val[lc[x]]>=val[rc[x]]){
		val[x]=val[lc[x]];
		ans[x]=ans[lc[x]];
		if(!val[x])ans[x]=0;
	}else{
		val[x]=val[rc[x]];
		ans[x]=ans[rc[x]];
		if(!val[x])ans[x]=0;
	}
}
int merge(int x,int y,int l,int r){
	if(!x)return y;if(!y)return x;
	if(l==r){val[x]+=val[y];if(val[x]>0)ans[x]=l;return x;}
	int mid=(l+r)>>1;
	lc[x]=merge(lc[x],lc[y],l,mid);
	rc[x]=merge(rc[x],rc[y],mid+1,r);
	if(val[lc[x]]>=val[rc[x]]){
		val[x]=val[lc[x]];
		ans[x]=ans[lc[x]];
		if(!val[x])ans[x]=0;
	}else{
		val[x]=val[rc[x]];
		ans[x]=ans[rc[x]];
		if(!val[x])ans[x]=0;
	}
	return x;
}
void DFS(int x){
	//printf("DFS in %d :",x);
	for(int i=h[x];i;i=e[i].next){
		int to=e[i].to;
		if(to==fa[x])continue;
		DFS(to);
		root[x]=merge(root[x],root[to],1,N);
	}
	ansn[x] = ans[root[x]];
	return;
}
void test(int x,int l,int r){
	printf("%d %d %d\n",l,r,val[x]);
	if(l==r)return ;
	int mid=(l+r)>>1;
	if(lc[x])test(lc[x],l,mid);
	if(rc[x])test(rc[x],mid+1,r);
	return;
}
int main(){
	//freopen("data.in","r",stdin);
	//freopen("data.out","w",stdout);
	n=read();m=read();
	for(int i=1,a,b;i<=n-1;i++){
		a=read(),b=read();
		add(a,b);add(b,a);
	}
	dep[1]=1;
	dfs1(1,1);dfs2(1,1);
	for(int i=1;i<=m;i++){
		X[i]=read(),Y[i]=read(),Z[i]=read();
		N=max(N,Z[i]);
	}

	for(int i=1;i<=m;i++){
		int x=X[i],y=Y[i],z=Z[i];
		change(root[x],1,N,z,1);
		change(root[y],1,N,z,1);
		int F=lca(x,y);
		change(root[F],1,N,z,-1);
		if(fa[F]!=F)change(root[fa[F]],1,N,z,-1);
	}
	DFS(1);
	for(int i=1;i<=n;i++){
		printf("%d\n",ansn[i]);
	}
	return 0;
}

2.P2824 [HEOI2016/TJOI2016]排序

Emm……當然可以二分答案+01序列排序來解決

但是,我們可是線段樹合併和分裂的學習筆記丫!

而且線段樹合併和分裂的速度甩了上一種不知道多少,而且還可以線上

先在每一個位置上建一棵權值線段樹,對於一段區間我們合併節點,並用標記來表示它是正序還是倒序,用set來維護現存所有區間的左端點.當要排序的區間跨過原本有序的區間時,按照位置分裂原區間再進行合併

複雜度\(O(nlog_{2}n)\)

#include<bits/stdc++.h>
using namespace std;
int const MAXN=1e5+10,MAXM=MAXN*70;
int n,m,tot,tott,_,q;
int val[MAXM],lc[MAXM],rc[MAXM];
int root[MAXN];
bool sta[MAXN];
set<int>S;
typedef set<int>::iterator Sit;
void insert(int &x,int l,int r,int p,int a){
	if(!x)x=++tot;
	if(l==r){val[x]+=a;return;}
	int mid=(l+r)>>1;
	if(p<=mid)insert(lc[x],l,mid,p,a);
	else insert(rc[x],mid+1,r,p,a);
	val[x]=val[lc[x]]+val[rc[x]];
}
void merge(int &x,int y){
	if(!(x&&y)){x|=y;return;}
	val[x]+=val[y];
	merge(lc[x],lc[y]);
	merge(rc[x],rc[y]);
}
void split(int x,int &y,int s,int op){
	if(val[x]==s)return;
	val[y=++tot]=val[x]-s;val[x]=s;
	if(op==0){
		if(val[lc[x]]>=s){split(lc[x],lc[y],s,op);rc[y]=rc[x];rc[x]=0;}
		else split(rc[x],rc[y],s-val[lc[x]],op);
	}else{
		if(val[rc[x]]>=s){split(rc[x],rc[y],s,op);lc[y]=lc[x],lc[x]=0;}
		else split(lc[x],lc[y],s-val[rc[x]],op);
	}
}
int query(int x,int l,int r){
	if(l==r)return l;
	int mid=(l+r)>>1;
	if(lc[x])return query(lc[x],l,mid);
	else return query(rc[x],mid+1,r);
}
Sit find(int p){
	Sit k=S.lower_bound(p);
	if(*k==p)return k;
	--k;split(root[*k],root[p],p-*k,sta[p]=sta[*k]);
	return S.insert(p).first;
}
int main(){
	scanf("%d%d",&n,&m);
	S.insert(n+1);
	for(int i=1;i<=n;i++){
		int x;scanf("%d",&x);
		//insert(root[i],1,n,x,1);
		insert(root[i],1,n,x,1);
		S.insert(i);
	}
	for(int i=1;i<=m;i++){
		int op,l,r;scanf("%d%d%d",&op,&l,&r);
		Sit nl=find(l),nr=find(r+1);
		for(Sit j=++nl;j!=nr;++j)merge(root[l],root[*j]);
		sta[l]=op;S.erase(nl,nr);
	}
	scanf("%d",&q);
	find(q);find(q+1);
	printf("%d\n",query(root[q],1,n));
	return 0;
}

參考資料