线段树合并
作者:@魔幻世界魔幻人生
本文为作者原创,转载请注明出处:https://www.cnblogs.com/subtlemaple/p/16340030.html
村落里的一共有 n 座房屋,并形成一个树状结构。然后救济粮分 mm 次发放,每次选择两个房屋 (x, y),然后对于 x 到 y 的路径上(含 x 和 y)每座房子里发放一袋 z 类型的救济粮。
然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮
题解
和管道运输那道题很像,使用树上差分:
树上差分
设 x 到 y ,那么求出 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
左右,具体为啥的话,以后可能我会知道吧。。。