[DS 小计] 线段树合并
引入
现在你有很多棵二叉树。
二叉树的节点总和是 \(n\) 。
现在,你要把它们合并。
怎么做呢?
实际上,写的好是可以 \(O(n)\) 完成的。
前置题目 1
给出 \(2\) 棵二叉树,合并两棵二叉树。
怎么做呢?
很容易的暴力,遍历每个点,合并即可。
合并我们进行以下分类讨论:
- 如果现在 \(u\) , \(v\) 中任意一左子树/右子树为空,直接接上去即可。
- 否则,递归处理。
这样时间复杂度是重复节点个数。
好像没什么用的样子
前置题目 2
给出 \(n\) 棵二叉树,合并 \(n\) 棵二叉树。节点总数为 \(m\) 。
其实很简单,我们按照上面合并两棵的思路,暴力合并即可。
为什么这样可行呢?时间复杂度不会炸吗?
我们来分析时间复杂度。
那么,我们合并两棵线段树的时间复杂度是确定的。
我们可以理解为,合并后的两个节点,我们删除了其中一个。
也就是说,我们给其中一个打上了标记。
显然,每个节点至多被打上一次标记。
所以时间复杂度是 \(O(m)\) 的。
复杂度的情况保证是不会重复合并两次二叉树。
正题
线段树合并和二叉树合并思想一致。
只是线段树要维护信息。
只需要这样做:
先下方当前节点的标记,然后往下递归合并。
这样我们能保证合并时的两个点没有标记,是正确的。
然后因为你已经下好标记了,然后更新左右子树,子树也是下方好标记/更新了的,直接上传标记(Pushup)即可。
所以,合并时间复杂度是总的点数。
这样就做完了。
具体的,到了叶子节点请特殊处理处理即可。
然后就做完了,完结撒花。
算法真正厉害的地方就是可以和 SAM 结合,后面会细🔒。
点击查看代码
#include<bits/stdc++.h>
#define N 100005
#define ll long long
#define ls(x) tr[x].r
#define rs(x) tr[x].l
using namespace std;
const int C=N-5;
int n,m;
struct tnode{
int l,r,c,v;
};
int root[N],tot=1;
struct segtree{
tnode tr[N*4*20];
void Pushup(int x)
{
if(tr[ls(x)].v>=tr[rs(x)].v)
tr[x].c=tr[ls(x)].c,tr[x].v=tr[ls(x)].v;
else tr[x].c=tr[rs(x)].c,tr[x].v=tr[rs(x)].v;
}
void modify(int &now,int l,int r,int p,int v)
{
if(!now) now=++tot;
if(l==r)
{
tr[now].v+=v;
tr[now].c=p;
return;
}
int mid=(l+r)/2;
if(p<=mid) modify(ls(now),l,mid,p,v);
else modify(rs(now),mid+1,r,p,v);
Pushup(now);
}
int merges(int u,int v,int l,int r)
{
if(!u) return v;
if(!v) return u;
if(l==r)
{
tr[u].v+=tr[v].v;
return u;
}
int mid=(l+r)/2;
ls(u)=merges(ls(u),ls(v),l,mid);
rs(u)=merges(rs(u),rs(v),mid+1,r);
Pushup(u);
return u;
}
}tr;
int head[N];
struct edge{
int to,next;
}e[N*2];
void add(int u,int v)
{
e[tot]=(edge){v,head[u]};
head[u]=tot++;
}
int f[N][20],dep[N];
int glca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=18;i>=0;i--)
if(dep[f[u][i]]>=dep[v]) u=f[u][i];
if(u==v) return u;
for(int i=18;i>=0;i--)
if(f[u][i]^f[v][i]) u=f[u][i],v=f[v][i];
return f[u][0];
}
void dfs1(int now,int fa)
{
dep[now]=dep[fa]+1;
f[now][0]=fa;
for(int i=1;f[now][i-1];i++)
f[now][i]=f[f[now][i-1]][i-1];
for(int i=head[now];i;i=e[i].next)
{
int son=e[i].to;
if(son==fa) continue;
dfs1(son,now);
}
}
int ans[N];
void dfs2(int now,int fa)
{
for(int i=head[now];i;i=e[i].next)
{
int son=e[i].to;
if(son==fa) continue;
dfs2(son,now);
root[now]=tr.merges(root[now],root[son],1,C);
}
if(tr.tr[root[now]].v)ans[now]=tr.tr[root[now]].c;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs1(1,0);
tot=0;
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
int lca=glca(u,v);
tr.modify(root[u],1,C,w,1);
tr.modify(root[v],1,C,w,1);
tr.modify(root[lca],1,C,w,-1);
tr.modify(root[f[lca][0]],1,C,w,-1);
}
dfs2(1,0);
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}