线段树合并(随笔)
线段树合并
来来来一百种线段树,线段树怎么这么博大精深啊
今天来讲一下线段树合并:
线段树合并意义:
顾名思义,我们现在有两棵线段树,我们合并他们,就是建一棵新的线段树来包含这两棵线段树的信息,不难发现,对于普通线段树,在同等规模下,两棵线段树的结构是相同的,这时候直接按位进行合并就行了,不需要设计算法来合并,所以这个算法是针对动态开点这种残缺不全的线段树来操作的
与之而来的有几个问题:
1.如何建立这一棵新的树?
2.对于两棵线段树的重合/空缺位置我们该这么处理?
假设合并要进行的是加操作,那么我们就把线段树 1 的值加入线段树 2 中构成新的线段树,那么如何实现呢?
我们同时在两颗线段树上进行 \(dfs\),进入一个节点时判断是否两棵树的该节点都有左儿子,如果都有就进去继续 \(dfs\),反之,若是树 1 没有左儿子或1,2树都没有,则无视此操作,若是树 2 没有左儿子,那直接把树 1 的左儿子赋给树 2 就行了。要是都没有左右节点,直接把 1 赋给 2 然后返回就行了,对于一个节点的 \(dfs\) 结束后,向上传递信息。
不好具体来阐述所以我们来看一道例题P4556 雨天的尾巴 /【模板】线段树合并 - 洛谷
乍一看有树上路径,很像树链剖分,事实上确实可以用树剖写,但是却是线段树合并模板?
暴力处理我们将\(a,b\)到根的所有节点加上权值,再让\(lca(a,b)\)和\(lca(a,b)\)的父亲到根的所有节点减去权值,若直接遍历时间复杂度是可想而知的大,所以我们可以进行树上差分,我们来看看如何用线段树优化,首先我们要进行操作的是权值线段树,对每一个节点都建一棵权值线段树,对应整个救济粮的值域区间,维护每种救济粮有多少,记为\(sum\),以及最多的救济粮的个数,记为 \(maxk\)
所以一次操作对应
$sum[x]++,sum[y]++,sum[x]++,sum[y]++ $
$ sum[lca(x,y)]−−,sum[fa[lca(x,y)]]−−$
仅在 4 个点上统计变化情况
在最后统计答案的时候,就用到了线段树合并,在每个节点线段树上将原先差分的结果加起来就是答案,具体实现见代码
果然还是树剖好理解
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int M=80*N;
struct Edge{
int to;
int next;
}e[N<<1];
int h[N];
int cnt;
int n,m;
struct SG{
int lson;
int rson;
int sum;
int maxk;
int l,r;
}tr[M];
int ans[N];
int root[N];
int f[N][21];
int dep[N];
void add(int a,int b){
e[cnt].to=b;
e[cnt].next=h[a];
h[a]=cnt++;
return ;
}
void pushup(int u){
int l=tr[u].lson;
int r=tr[u].rson;
if(tr[l].sum>=tr[r].sum){
tr[u].sum=tr[l].sum;
tr[u].maxk=tr[l].maxk;
}
else{
tr[u].sum=tr[r].sum;
tr[u].maxk=tr[r].maxk;
}
//合并求区间众数,具体为什么不用更新sum的原因是
//在modify中 l==r 时这单个数的贡献已经统计完了
//后面区间变大时不会出现使这个数贡献增加的情况
}
void modify(int &u,int l,int r,int z,int val){
if(!u) u=++cnt;
if(l==r){
tr[u].sum+=val;
tr[u].maxk=z;
return ;
}
int mid=l+r>>1;
if(z<=mid) modify(tr[u].lson,l,mid,z,val);
else modify(tr[u].rson,mid+1,r,z,val);
pushup(u);
}
int merge(int u,int v,int l,int r){
if(!u||!v) return u+v;//左/右子节点没有直接加然后返回不会错
if(l==r){
tr[u].sum+=tr[v].sum;//叶节点加权然后返回
return u;
}
int mid=l+r>>1;
tr[u].lson=merge(tr[u].lson,tr[v].lson,l,mid);// dfs下去 ,用线段树的遍历方式
tr[u].rson=merge(tr[u].rson,tr[v].rson,mid+1,r);
pushup(u);//合并答案
return u;
}
void calc(int u,int fa){
for(int i=h[u];~i;i=e[i].next){
int v=e[i].to;
if(v==fa) continue;
calc(v,u);//注意是先遍历,从子节点到父节点,这是显而易见的
root[u]=merge(root[u],root[v],1,1e5); //从头到脚合并一遍
}
ans[u]=tr[root[u]].maxk;
if(!tr[root[u]].sum){
ans[u]=0;
}
}
void dfs(int u,int fa){
f[u][0]=fa;
dep[u]=dep[fa]+1;
for(int i=1;i<20;i++){
f[u][i]=f[f[u][i-1]][i-1];//预处理,倍增跳LCA
}
for(int i=h[u];~i;i=e[i].next){
int v=e[i].to;
if(v==fa){
continue;
}
dfs(v,u);
}
}
int lca(int a,int b){
if(dep[a]<dep[b]){
swap(a,b);
}
for(int i=19;i>=0;i--){
if(dep[f[a][i]]>=dep[b]){
a=f[a][i];
}
}
if(a==b) return a;
for(int i=19;i>=0;i--){
if(f[a][i]!=f[b][i]){
a=f[a][i];
b=f[b][i];
}
}
return f[a][0];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
dfs(1,0);
while(m--){
int x,y,z;
cin>>x>>y>>z;
int k=lca(x,y);
modify(root[x],1,1e5,z,1);
modify(root[y],1,1e5,z,1);
modify(root[k],1,1e5,z,-1);
modify(root[f[k][0]],1,1e5,z,-1);
}
calc(1,0);
for(int i=1;i<=n;i++){
cout<<ans[i]<<endl;;
}
return 0;
}

浙公网安备 33010602011771号