- C20250045's blog
换根DP
- 2023-3-23 23:30:54 @
0 前言
换根dp应该是树形dp中比较难理解的一类dp了。当然,理解了之后就没有难度了
1 引入
给出一个 个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大
我们先来看看 的暴力怎么做吧
我们珂以枚举每一个点,将其作为根,然后求出每一个点的深度然后统计深度和就行了
再一看数据范围 ,显然无法通过
那么,我们应该如何来优化呢?
2 换根dp
我们可以利用一些技巧来把这一类问题优化到 的时间内解决
接下来,我们就要引入换根dp的一些思想 (或者叫套路) 了
换根dp一般分为三个步骤
1、先指定一个根节点
2、一次dfs统计子树内的节点对当前节点的贡献
3、一次dfs统计父亲节点对当前节点的贡献并合并统计最终答案
光这么说可能不太好理解,我们来结合上面的那个例子看看吧
我们先来看一个节点 的子树里面的节点对它的贡献:
很明显,这个贡献就是子树里所有节点到 的深度和,我们把它记为 (不过待会会发现根本不需要这个 )
接着,我们记 为以 为根的子树大小, 为点 到 号节点(我们指定的根节点)的深度(之后有用)
接下来,我们来考虑第2次dfs,也就是计算父亲对它的贡献
我们令 为以 为根节点的深度和
所以,我们令 为 的一个儿子节点,可得 )
为啥打上了括号呢?是因为这样更好理解了
如果看不懂,我来解释一下
是以 为根的子树深度和,显然要加上 是父亲 节点的答案,显然要减去我们 子树里的信息(不然就多算了)
那么, 就是我们 子树的信息了
有人就要问了,子树里的的信息不就是 吗?
但是我们是从父亲的答案减去,显然在以 为根时, 的子树中的所有节点的深度都有加一,于是就增加 ,同理,后面的 就是非 节点子树中的点啦,和上面一样理解一下就好啦qwq
解释完了,我们化简一下这个柿子 发现可以不用记录 。于是,我们就可以在 的时间内搞定啦
还有什么不懂的地方我们稍微放一点核心代码吧
void dp1(int u,int fa)
{
sz[u]=1;
for(int i=Head[u];i;i=Edge[i].next)
{
int v=Edge[i].to;
if(v==fa)continue;
dep[v]=dep[u]+1;//深度
dp1(v,u);
sz[u]+=sz[v];//子树大小
}
}
void dp2(int u,int fa)
{
for(int i=Head[u];i;i=Edge[i].next)
{
int v=Edge[i].to;
if(v==fa)continue;
f[v]=f[u]-2*sz[v]+sz[1];//转移方程
dp2(v,u);
}
}
in main:
dp1(1,0);
for(int i=1;i<=n;i++)f[1]+=dep[i];//计算1号节点的答案
dp2(1,0);
int ans=-19260817,id=0;
for(int i=1;i<=n;i++)
if(ans<f[i])ans=f[i],id=i;//统计最终的答案
3 总结&例题
总结一下,换根dp主要是用来解决树上根不确定的时候的dp问题,通常可以将 的暴力统计做到 的dp
换根dp一共分成3步:
1、选择一个节点作为根
2、统计子树内节点对它的贡献
3、统计父亲节点对它的贡献并计算最终答案
理解了这些步骤,我们就可以来切做几道题啦
【例题1】Great Cow Gathering
这题和上面那题就非常类似了,只是点和边都加上了权值
理解上面那题这题就不难理解了
【例题2】Nearby Cows
也是一道挺简单的换根dp题
我们令 为以 为根,距离 为 的节点权值和
也是先计算 的子树对 的贡献, , 为 的子节点
接下来,我们计算父亲节点对它的贡献, ,其中, 为 的父亲
最后,我们还要去重 (留给读者自行思考,并不难想)
总时间复杂度
【例题3】Kamp
一道比较烦的换根dp
分类讨论其实很烦,若要看这题完整的解法,可以看我这篇题解
这里我们令 为以 为根的子树中从 开始把所有家在这个子树内的人送回家并回到 节点的最短路程
接下来,由于车子出发之后不一定要回到原来的出发点,所以要统计最长链和次长链,接下来再来一次dfs合并答案即可
【例题4】连珠线
一道比较难的换根dp,也是只提思路
我们令 为 节点是/不是蓝线中点的答案
接下来,令 为 节点是/不是蓝线中点的不考虑 这个儿子的答案
记录一下不考虑每个儿子的最大值,然后合并一下答案就行了,还要看具体的做法可以看题解
4 结尾
换根dp确实是一种难理解的树形dp,但是很多题目都要用到这个技巧,所以必须要学啊