UltiMadow
2020-03-18 15:51:17
换根dp应该是树形dp中比较难理解的一类dp了
当然,理解了之后就没有难度了
我们来看这个题
给出一个 N 个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大
我们先来看看
我们珂以枚举每一个点,将其作为根,然后求出每一个点的深度然后统计深度和就行了
再一看数据范围
那么,我们应该如何来优化呢?
我们可以利用一些技巧来把这一类问题优化到
接下来,我们就要引入换根dp的一些思想 (或者叫套路) 了
换根dp一般分为三个步骤
1、先指定一个根节点
2、一次dfs统计子树内的节点对当前节点的贡献
3、一次dfs统计父亲节点对当前节点的贡献并合并统计最终答案
光这么说可能不太好理解,我们来结合上面的那个例子看看吧
我们先来看一个节点
很明显,这个贡献就是子树里所有节点到
接着,我们记
接下来,我们来考虑第2次dfs,也就是计算父亲对它的贡献
我们令
所以,我们令
为啥打上了括号呢?是因为这样更好理解了
如果看不懂,我来解释一下
同理,后面的
解释完了,我们化简一下这个柿子
发现可以不用记录
于是,我们就可以在
还有什么不懂的地方我们稍微放一点核心代码吧
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;//统计最终的答案
总结一下,换根dp主要是用来解决树上根不确定的时候的dp问题,通常可以将
换根dp一共分成3步:
1、选择一个节点作为根
2、统计子树内节点对它的贡献
3、统计父亲节点对它的贡献并计算最终答案
理解了这些步骤,我们就可以来切做几道题啦
【例题1】Great Cow Gathering
这题和上面那题就非常类似了,只是点和边都加上了权值
理解上面那题这题就不难理解了
【例题2】Nearby Cows
也是一道挺简单的换根dp题
我们令
也是先计算
接下来,我们计算父亲节点对它的贡献,
最后,我们还要去重 (留给读者自行思考,并不难想)
总时间复杂度
【例题3】Kamp
一道比较烦的换根dp
分类讨论其实很烦,若要看这题完整的解法,可以看我这篇题解
这里我们令
接下来,由于车子出发之后不一定要回到原来的出发点,所以要统计最长链和次长链,接下来再来一次dfs合并答案即可
【例题4】连珠线
一道比较难的换根dp,也是只提思路
我们令
接下来,令
记录一下不考虑每个儿子的最大值,然后合并一下答案就行了
还要看具体的做法可以看题解
换根dp确实是一种难理解的树形dp,但是很多题目都要用到这个技巧,所以必须要学啊