[模板] 线段树合并
[模板] 线段树合并
传送门--雨天的尾巴
一些概念与心得
-
线段树合并顾名思义,将两棵线段树进行信息合并的过程(具体来说可以是把一个线段树接到另一个线段树上的过程)。
-
由于内存量一般过大,使用 动态开点线段树,用以节省大量内存(可以采用 引用“&” 的方式)。
-
合并时尽量避免多开点,因此需要像主席树一样接到另一棵子树上。
-
维护多棵线段树,裆燃需要维护每棵树的根节点。
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\)
-
-
线段树合并与树上差分有时在处理树上路径时有些联系。