线段树合并

线段树合并

前置知识:权值线段树,动态开点线段树
学完也可以顺便把主席树学了

[Vani有约会]雨天的尾巴 /【模板】线段树合并

这是一道模板题,但首先得接触过树上差分。。
不会的建议学一下,不是什么很复杂的东西
我们知道,我们的点差分需要在路径的两端点加,lca减,lca的父亲减
但是这里有类型的限制,相同的类型才叠加在一起
我们对每一个树上的节点建一颗动态开点的权值线段树,权值表类型,里面记录类型个数
不然你空间没了
动态开点可以接受的原因是我们没插进一个值最多改变一颗线段树log个节点
那么由于我们做了树上差分,我们需要把子节点的信息传递给父节点
这里我们需要把子节点的线段树加到父结点的线段树上
那么这就需要线段树合并

核心做法


上图中的第一颗线段树与第二颗线段树的合并用了一个”+“表示
而数字表示每一个节点所维护的信息
得到的合并后的线段树的节点中有维护原树两个点信息的,都是我们需要遍历的点
而只维护了一个点信息的,代表某一棵树在这个位置没有节点即没有信息
我们直接指向另一个子节点即可(如果有接触过主席树这里应该很好理解)

int merge(int x,int y,int l,int r){
	if(x==0||y==0)return x+y;//代表某棵待合并的线段树这个位置无信息
	if(l==r){tree[x].mx+=tree[y].mx;return x;}//回溯
	int mid=l+((r-l)>>1);
	tree[x].ls=merge(tree[x].ls,tree[y].ls,l,mid);
	tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
    //这里和回溯的时候对于此题不需要新建节点,因为每棵线段树只用这一次就行
	pushup(x);//更新节点数据
	return x;
}

这是这个题的合并(merge)函数
而一般的题可能要在合并的过程中新建节点

时间复杂度分析

感谢hzr大佬的分享
本题对于每一条路径我们都可能增加最多4*logw个节点,w为类型的大小
则线段树(森林?)一共可能有mlogw个节点
对于加点显然是O(mlogw)的
而对于合并,我的dfs长这样

void dfs2(int x,int fa){
	for(int i=head[x];i!=-1;i=e[i].nxt){
		int tmp=e[i].to;
		if(tmp==fa)continue;
		dfs2(tmp,x);
		rt[x]=merge(rt[x],rt[tmp],1,p);
	}
	ANS[x]=tree[rt[x]].ans;
	if(tree[rt[x]].mx==0)ANS[x]=0;
	return ;
}

即每次合并自己的所有子节点
我们计算merge里的遍历次数
我们把父节点与子节点的合并的遍历计算在子节点上
那么每一颗线段树最多被遍历一次
可能有人会对merge中的第一行的复杂度有疑问
注意,这里并没有对下面的子树继续遍历,而是直接返回
一共mlogw个节点,故复杂度O(mlogw)

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005,maxm=100005,inf=0x3f3f3f3f;
struct node{
	int ls,rs;
	int mx,ans;
}tree[80*maxn];
int tot;
int rt[maxn];
struct upd{
	int u,v,w,z;
	bool operator <(upd i)const{
		return w<i.w;
	}
}a[maxm];
struct Edge{
	int to,nxt;
}e[2*maxn];
int cnt;
int head[maxn];
int p=0;
int dep[maxn];
int f[maxn][21];
int val[maxm];
int ANS[maxn];
int read(){
	int x=0,y=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')y=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*y;
}
void add(int u,int v){
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt++;
	return ;
}
void dfs1(int x,int fa){
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for(int i=1;i<=20;i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for(int i=head[x];i!=-1;i=e[i].nxt){
		int tmp=e[i].to;
		if(tmp==fa)continue;
		dfs1(tmp,x);
	}
	return ;
}
int lca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=20;i>=0;i--)
		if(dep[f[x][i]]>=dep[y])
			x=f[x][i];
	if(x==y)return x;
	for(int i=20;i>=0;i--)
		if(f[x][i]!=f[y][i]){
			x=f[x][i];
			y=f[y][i];
		}
	return f[x][0];
}
int newnode(){
	tot++;
	tree[tot].ls=tree[tot].rs=0;
	tree[tot].mx=tree[tot].ans=0;
	return tot;
}
void pushup(int k){
	int ls=tree[k].ls,rs=tree[k].rs;
	tree[k].mx=max(tree[ls].mx,tree[rs].mx);
	if(tree[k].mx==0)
		tree[k].ans=0;
	else if(tree[k].mx==tree[ls].mx)
		tree[k].ans=tree[ls].ans;
	else
		tree[k].ans=tree[rs].ans;
	return ;
}
void modify(int &k,int l,int r,int p,int val){
	if(!k)k=newnode();
	if(l==r){tree[k].mx+=val;tree[k].ans=p;return ;}
	int mid=l+((r-l)>>1);
	if(p<=mid)modify(tree[k].ls,l,mid,p,val);
	else modify(tree[k].rs,mid+1,r,p,val);
	pushup(k);
	return ;
}
int merge(int x,int y,int l,int r){
	if(x==0||y==0)return x+y;
	if(l==r){tree[x].mx+=tree[y].mx;return x;}
	int mid=l+((r-l)>>1);
	tree[x].ls=merge(tree[x].ls,tree[y].ls,l,mid);
	tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
	pushup(x);
	return x;
}
void dfs2(int x,int fa){
	for(int i=head[x];i!=-1;i=e[i].nxt){
		int tmp=e[i].to;
		if(tmp==fa)continue;
		dfs2(tmp,x);
		rt[x]=merge(rt[x],rt[tmp],1,p);
	}
	ANS[x]=tree[rt[x]].ans;
	if(tree[rt[x]].mx==0)ANS[x]=0;
	return ;
}
int main(){
	int n,m;
	n=read();m=read();
	memset(head,-1,sizeof(head));
	for(int i=1;i<n;i++){
		int u,v;
		u=read();v=read();
		add(u,v);
		add(v,u);
	}
	dfs1(1,0);
	for(int i=1;i<=m;i++){
		a[i].u=read();
		a[i].v=read();
		a[i].w=read();
	}
	sort(a+1,a+m+1);
	a[0].w=0;
	for(int i=1;i<=m;i++){
		if(a[i].w>a[i-1].w)p++;
		a[i].z=p;
		val[p]=a[i].w;
	}
	tree[0].mx=0;tree[0].ans=0;
	for(int i=1;i<=m;i++){
		int LCA=lca(a[i].u,a[i].v);
		modify(rt[a[i].u],1,p,a[i].z,1);
		modify(rt[a[i].v],1,p,a[i].z,1);
		modify(rt[LCA],1,p,a[i].z,-1);
		if(f[LCA][0])modify(rt[f[LCA][0]],1,p,a[i].z,-1);
	}
	dfs2(1,0);
	for(int i=1;i<=n;i++)
		printf("%d\n",val[ANS[i]]);
	return 0;
}

例题[HNOI2012]永无乡

一句话题意:无向图内动态加边,询问某个点所在连通块内的权值第k小
我们可以用并查集判断每一个点在哪一个连通块里
对于每一个连通块建一颗权值线段树可以在O(logn)的时间内得到区间的权值第k小
当两个连通块合并时,我们用线段树合并将两颗线段树合并起来
这里我们可以把并查集的合并过程建立出一棵树
我们的线段树合并相当于从叶子节点往上一步一步合并到根节点,复杂度和模板题一样

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
struct node{
	int ls,rs;
	int num;
}t[20*maxn];
int tot;
int rt[maxn];
int f[maxn];
int val[maxn];
int read(){
	int x=0,y=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')y=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*y;
}
int find(int x){
	if(f[x]==x)return x;
	return f[x]=find(f[x]);
}
int newnode(){
	tot++;
	t[tot].ls=t[tot].rs=0;
	t[tot].num=0;
	return tot;
}
void pushup(int k){
	t[k].num=t[t[k].ls].num+t[t[k].rs].num;
	return ;
}
void modify(int &k,int l,int r,int p){
	if(!k)k=newnode();
	if(l==r){t[k].num++;return ;}
	int mid=l+((r-l)>>1);
	if(p<=mid)modify(t[k].ls,l,mid,p);
	else modify(t[k].rs,mid+1,r,p);
	pushup(k);
	return ;
}
int merge(int x,int y,int l,int r){
	if(x==0||y==0)return x+y;
	if(l==r){t[x].num+=t[y].num;return x;}
	int mid=l+((r-l)>>1);
	t[x].ls=merge(t[x].ls,t[y].ls,l,mid);
	t[x].rs=merge(t[x].rs,t[y].rs,mid+1,r);
	pushup(x);
	return x;
}
int query(int k,int l,int r,int p){
	if(t[k].num<p)return -1;
	if(l==r)return val[l];
	int mid=l+((r-l)>>1);
	int ls=t[k].ls,rs=t[k].rs;
	if(t[ls].num>=p)return query(ls,l,mid,p);
	return query(rs,mid+1,r,p-t[ls].num);
}
int main(){
	int n,m,q;
	n=read();m=read();
	for(int i=1;i<=n;i++){
		int rk;
		rk=read();
		val[rk]=i;
		f[i]=i;
		modify(rt[i],1,n,rk);
	}
	for(int i=1;i<=m;i++){
		int u,v;
		u=find(read());v=find(read());
		if(u==v)continue;
		f[v]=u;
		rt[u]=merge(rt[u],rt[v],1,n);
	}
	q=read();
	for(int i=1;i<=q;i++){
		char opt;
		int x,y;
		cin>>opt;
		x=find(read());y=read();
		if(opt=='Q')printf("%d\n",query(rt[x],1,n,y));
		else{
			y=find(y);
			if(x==y)continue;
			f[y]=x;
			rt[x]=merge(rt[x],rt[y],1,n);
		}
	}
	return 0;
}

[POI2011]ROT-Tree Rotations

对于一个节点i,我们可以交换它的两个子树
而对于节点i的操作,只能影响到左(右)边边整体对右(左)边整体的逆序对贡献
也就是说,我们假如知道右边有哪些权值和左边有那些权值,对交换子树前后算一下贡献
判断出对于这个节点i该不该交换子树即可
想知道一颗子树的权值情况我们可以用一颗权值线段树来表示
在线段树合并时计算交换或不交换两种情况的贡献就可以了

#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
struct seg{
	int ls,rs;
	long long num;
}t[20*2*maxn];
int tot;
int rt[2*maxn];
struct Edge{
	int to,nxt;
}e[2*maxn];
int cnt;
int head[2*maxn];
int n,m;
long long ans;
int read(){
	int x=0,y=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')y=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*y;
}
int newnode(){
	tot++;
	t[tot].ls=t[tot].rs=t[tot].num=0;
	return tot;
}
void pushup(int k){
	t[k].num=t[t[k].ls].num+t[t[k].rs].num;
	return ;
}
void modify(int &k,int l,int r,int val){
	if(!k)k=newnode();
	if(l==r){t[k].num++;return ;}
	int mid=l+((r-l)>>1);
	if(val<=mid)modify(t[k].ls,l,mid,val);
	else modify(t[k].rs,mid+1,r,val);
	pushup(k);
	return ;
}
int merge(int x,int y,int l,int r,long long &tmp1,long long &tmp2){
	if(x==0||y==0)return x+y;
	if(l==r){t[x].num+=t[y].num;return x;}
	int mid=l+((r-l)>>1);
	tmp1+=t[t[y].rs].num*t[t[x].ls].num;
	tmp2+=t[t[x].rs].num*t[t[y].ls].num;
	t[x].ls=merge(t[x].ls,t[y].ls,l,mid,tmp1,tmp2);
	t[x].rs=merge(t[x].rs,t[y].rs,mid+1,r,tmp1,tmp2);
	pushup(x);
	return x;
}
void add(int u,int v){
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt++;
	return ;
}
void build_tree(){
	int u=++m;
	int k;
	k=read();
	if(!k){
		add(u,m+1);
		build_tree();
		add(u,m+1);
		build_tree();
	}
	else
		modify(rt[u],1,n,k);
	return ;
}
void dfs(int x){
	long long tmp1=0,tmp2=0;
	for(int i=head[x];i!=-1;i=e[i].nxt){
		int tmp=e[i].to;
		dfs(tmp);
		rt[x]=merge(rt[x],rt[tmp],1,n,tmp1,tmp2);
	}
	ans+=min(tmp1,tmp2);
	return ;
}
int main(){
	n=read();
	memset(head,-1,sizeof(head));
	t[0].ls=t[0].rs=t[0].num=0;
	build_tree();
	dfs(1);
	printf("%lld",ans);
	return 0;
}