返回顶部

线段树合并

 


作者:@魔幻世界魔幻人生
本文为作者原创,转载请注明出处:https://www.cnblogs.com/subtlemaple/p/16340030.html


线段树合并

例题

P4556 Vani有约会]雨天的尾巴

村落里的一共有 n 座房屋,并形成一个树状结构。然后救济粮分 mm 次发放,每次选择两个房屋 (x, y),然后对于 xy 的路径上(含 xy)每座房子里发放一袋 z 类型的救济粮。

然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮

 

题解

和管道运输那道题很像,使用树上差分:

树上差分

xy ,那么求出 x,y 的最近公共祖先 p , x 和 y 加 1 ,p 和 fa[p] 减一

然后使用 dfs 求差分的前缀和

这里有个问题,要是分别记录每个节点的救济粮个数,dfs时需要分别合并,复杂度不允许

但是我们可以转换思路:

每个节点 都看成 一棵线段树

那么线段树的线段区间是什么呢?

每种救济粮的编号

那么就可以用动态开点建立权值线段树(因为粮食是可以重复的元素,而我们只需要它们的数量)

我们设一个change()函数来进行差分建立

w[] 代表当前节点的区间内最多的粮食的数目, mxf[] 代表当前节点最多的粮食编号

这里我最开始担心:如果不是叶子节点,合并的时候w[] 不是会出错吗?

但我写的时候才发现push_up() 解决了这个问题

push_up():左右子树必然没有重合元素,那么我们维护最大值的时候直接比较、替换就可以了

差分时还有个问题:LCA

我参考洛谷第一篇题解写了树链剖分,算LCA时跳链就好了

il int LCA(int x,int y)
{
while(tp[x]!=tp[y])//不在同一链时
{
if(dep[tp[x]]<dep[tp[y]]) swap(x,y);//从深链跳到浅链
x=fa[tp[x]];//深链向上跳
}
if(dep[x]<dep[y]) return x;//同一链上,深度低是祖先
return y;
}

差分建立完成了

 

线段树合并

什么时候合并?dfs求树上前缀和的时候

合并完了怎么办?直接把节点的mxf[]传给答案数组match[]

谁和谁合并?父节点和子节点合并

合并的是什么?线段树

谁的线段树?上面说到:给每个点的粮食建立了一个权值线段树

怎么合并?

看下面的代码

xid代表父亲顺序不能搞混,要把子节点加到父亲上

int merge(int xid,int yid,int l,int r)//线段树合并 
{
if(!xid || !yid) return xid|yid;// 0|任何数==该数
if(l==r)
{
w[xid]+=w[yid]; mxf[xid]=l; return xid;
}
int mid=l+r>>1;
ls[xid]=merge(ls[xid],ls[yid],l,mid);//合并后要返回给父亲重连链表
rs[xid]=merge(rs[xid],rs[yid],mid+1,r);
push_up(xid);//合并后更新
return xid;//我们选择合并主体为x(父亲节点
}

当然是对应区间进行合并,

所以当一棵树的对应区间空了,直接拿另一个区间连接到父节点的链表

(每次合并操作都要把合并出来的树的标号id返回给父亲,以此来更新父节点的链表)

合并发现到叶子节点(也就是一种粮食的老家)时,直接大胆地把w[]加一起就行了

如果不是叶子节点,那就需要先给左右子树对应合并,当递归回来的时候push_up()

左右子树合并时也别忘了:把合并后的标号返回给父亲

 

坑点

我万万没想到数组上限开小了

动态开点涉及以下几个数组

w[M],mxf[M],ls[M],rs[M]

根据我的玄学测试,这个M大概是maxn*47左右,具体为啥的话,以后可能我会知道吧。。。

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#define il inline
using namespace std;
const int maxn=1e5+7;
const int M=6000005;
il int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
/////////////////////////////////
namespace MGW{

int n,m;
int X[maxn],Y[maxn],Z[maxn],ran;
int siz[maxn],dep[maxn],tp[maxn],fa[maxn],son[maxn];
int w[M];
int mxf[M];//一个节点的最大数量食物的标号
int rt[maxn],ls[M],rs[M];
int match[maxn];
int h[maxn],QXXcnt;
struct Edge{
int to,nxt;
}e[maxn<<1];
il void addedge(int x,int y){
++QXXcnt;
e[QXXcnt].to=y,e[QXXcnt].nxt=h[x];
h[x]=QXXcnt;
}
////////////////////
//

void dfs1(int x)
{
siz[x]=1;
int maxson=-1;
for(int i=h[x]; i ; i=e[i].nxt)
{
int v=e[i].to;
if(!dep[v])//没被访问过,所以没深度
{
dep[v]=dep[x]+1;
fa[v]=x;//初始化
dfs1(v);
siz[x]+=siz[v];
if(siz[v]>maxson)
{
maxson=siz[v]; son[x]=v;
}
}
}
}
void dfs2(int x,int tp_fa)
{
tp[x]=tp_fa;
if(!son[x]) return;
dfs2(son[x],tp_fa);
for(int i=h[x];i;i=e[i].nxt)
{
int v=e[i].to;
if(!tp[v])
   dfs2(v,v);//轻链的顶点开始构建
}
}
il int LCA(int x,int y)
{
while(tp[x]!=tp[y])
{
if(dep[tp[x]]<dep[tp[y]]) swap(x,y);
x=fa[tp[x]];//从轻链跳到重链
}
if(dep[x]<dep[y]) return x;//同一链上,深度低是祖先
return y;
}

il void push_up(int id)
{
if(w[ls[id]] >= w[rs[id]])
w[id]=w[ls[id]], mxf[id]=mxf[ls[id]];
else
   w[id]=w[rs[id]], mxf[id]=mxf[rs[id]];
}

int cnt;
int change(int id,int l,int r,int pos,int val)
{
if(!id) id=++cnt;
if(l==r) //到该粮食的叶子节点
{
w[id]+=val; mxf[id]=l;
return id;
}
int mid=l+r>>1;
if(pos<=mid)
ls[id]=change(ls[id],l,mid,pos,val);
else
   rs[id]=change(rs[id],mid+1,r,pos,val);
push_up(id);
return id;
}

int merge(int xid,int yid,int l,int r)//线段树合并
{
if(!xid || !yid) return xid|yid;
if(l==r)
{
w[xid]+=w[yid]; mxf[xid]=l; return xid;
}
int mid=l+r>>1;
ls[xid]=merge(ls[xid],ls[yid],l,mid);
rs[xid]=merge(rs[xid],rs[yid],mid+1,r);
push_up(xid);//合并后更新
return xid;//我们选择合并主体为x(其实无所谓
}

void dfs3(int x)
{
for(int i=h[x]; i ; i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]>dep[x])
{
dfs3(v);
   rt[x]=merge(rt[x],rt[v],1,ran);//合并,相当于加法
}
if(w[rt[x]]>0) match[x]=mxf[rt[x]];
}
}


void work()
{
n=read(),m=read();
for(int i=1;i<n;++i) {
int x,y;
x=read(),y=read();
addedge(x,y);
addedge(y,x);
}

dep[1]=1;
dfs1(1),dfs2(1,1);

for(int i=1;i<=m;++i) {
X[i]=read(),Y[i]=read(),Z[i]=read();
ran=max(ran,Z[i]);//用粮食类型作线段树下标,需知道下标最大值
}
for(int i=1;i<=m;++i)
{
int lca=LCA(X[i],Y[i]);
rt[X[i]]=change(rt[X[i]],1,ran,Z[i],1);
rt[Y[i]]=change(rt[Y[i]],1,ran,Z[i],1);
rt[lca]=change(rt[lca],1,ran,Z[i],-1);
if(fa[lca])//如果存在
   rt[fa[lca]]=change(rt[fa[lca]],1,ran,Z[i],-1);
} //树上差分完成
dfs3(1);//树上前缀和
for(int i=1;i<=n;i++) printf("%d\n",match[i]);
}
}
int main()
{
//freopen("data.in","r",stdin);
MGW::work();
return 0;
}
 
posted @   魔幻世界魔幻人生  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示