[模板] 线段树合并

[模板] 线段树合并

传送门--雨天的尾巴

一些概念与心得

  • 线段树合并顾名思义,将两棵线段树进行信息合并的过程(具体来说可以是把一个线段树接到另一个线段树上的过程)。

  • 由于内存量一般过大,使用 动态开点线段树,用以节省大量内存(可以采用 引用“&” 的方式)。

  • 合并时尽量避免多开点,因此需要像主席树一样接到另一棵子树上。

  • 维护多棵线段树,裆燃需要维护每棵树的根节点。

Sollution

对于这道题来讲:

  • 对每个节点维护一棵权值线段树,范围为 \(1-1e5\)

  • 对其进行树上差分操作,从底端向上合并线段树统计答案。

  • 线段树需要借助救济粮的 个数 辅助 \(pushup\)

小插曲--树上差分

树上差分有两种类型。

对于差分

如果想要对 \((u,v)\) 的路径进行操作,需要:

tmp[u]++,tmp[v]++,tmp[lca(u,v)]-=2

对于差分

tmp[u]++,tmp[v]++,tmp[lca(u,v)]--,tmp[fa[lca(u,v)]]--;

这里感谢 Danny_boodman 的图 因为我懒得画

线段树合并的操作

裆燃主要就是 \(merge\) 操作了,其他与动态开点线段树大同小异。

int merge(int x,int y,int l,int r){
	if(!x||!y)return x|y;
	if(l==r){
		sum[x]+=sum[y];return x;
	}
	int mid=(l+r)>>1;
	ls[x]=merge(ls[x],ls[y],l,mid);
	rs[x]=merge(rs[x],rs[y],mid+1,r);
	pushup(x);
    return x;
}
  • 合并过程中需要传四个参数:

    • 当前第一棵树的子树的根

    • 当前第二棵树的子树的根

    • 当前维护的区间

需要注意的是,类似于左偏树,需要考虑空节点的情况

然后返回当前根节点,别忘了 \(pushup\)

所以总代码长这样:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
	T x=0;char ch=getchar();bool fl=false;
	while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
	return fl?-x:x;
}
const int maxn = 1e5 + 10 , maxlog = 19 ;
int head[maxn],cnt=0;
struct edge{
	int to,nxt;
}e[maxn<<1];
inline void link(int u,int v){
	e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
}
int n,m;
const int maxm = maxn * 50 , maxc = 1e5;
int id[maxn],c[maxm],sum[maxm],ls[maxm],rs[maxm],num=0;
void pushup(int p){
	if(!rs[p]){
		sum[p]=sum[ls[p]];c[p]=c[ls[p]];return ;
	}
	if(!ls[p]){
		sum[p]=sum[rs[p]];c[p]=c[rs[p]];return ;
	}
	if(sum[ls[p]]>=sum[rs[p]]){
		sum[p]=sum[ls[p]];c[p]=c[ls[p]];
	}
	else{
		sum[p]=sum[rs[p]];c[p]=c[rs[p]];
	}
	return ;
}
#define mid ((l+r)>>1)
void update(int &p,int l,int r,int pos,int val){
	if(!p)p=++num;
	if(l==r){
		sum[p]+=val;c[p]=pos;return ;
	}
	if(pos<=mid)update(ls[p],l,mid,pos,val);
	else update(rs[p],mid+1,r,pos,val);
	pushup(p);
}
int merge(int x,int y,int l,int r){
	if(!x||!y)return x|y;
	if(l==r){
		sum[x]+=sum[y];return x;
	}
	ls[x]=merge(ls[x],ls[y],l,mid);
	rs[x]=merge(rs[x],rs[y],mid+1,r);
	pushup(x);
	return x;
}
int f[maxn][maxlog],de[maxn];
void dfs(int u,int fa){
	f[u][0]=fa;de[u]=de[fa]+1;
	for(int i=1;i<maxlog;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		dfs(v,u);
	}
}
int LCA(int x,int y){
	if(de[x]<de[y])swap(x,y);
	for(int i=maxlog-1;i>=0;i--)
		if(de[f[x][i]]>=de[y])x=f[x][i];
	if(x==y)return x;//
	for(int i=maxlog-1;i>=0;i--)
		if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
int ans[maxn];
void solve(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		solve(v,u);
		id[u]=merge(id[u],id[v],1,maxc);
	}
	ans[u]=c[id[u]];
	if(sum[id[u]]==0)ans[u]=0;//细节
}
#define read() read<int>()
int main(){
	n=read();m=read();
	for(int i=1,u,v;i<n;i++){
		u=read(),v=read();
		link(u,v);link(v,u);
	}
	dfs(1,0);
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),z=read();
		int lca=LCA(x,y);
		//printf("lca : %d\n",lca);
		update(id[x],1,maxc,z,1);
		update(id[y],1,maxc,z,1);
		update(id[lca],1,maxc,z,-1);
		update(id[f[lca][0]],1,maxc,z,-1);
	}
	solve(1,0);
	for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
	return 0;
}

后记

  • 树上路径问题往两个方面想

    • 树(点)分治

    • 树上差分

    • 树上莫队(还不会)

    • \(\cdots\)

  • 线段树合并与树上差分有时在处理树上路径时有些联系。

posted @ 2021-08-12 17:27  ¶凉笙  阅读(63)  评论(0编辑  收藏  举报