0 前言

换根dp应该是树形dp中比较难理解的一类dp了。当然,理解了之后就没有难度了

1 引入

给出一个 NN 个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大

我们先来看看 Θ(n2)\Theta(n^2)的暴力怎么做吧

我们珂以枚举每一个点,将其作为根,然后求出每一个点的深度然后统计深度和就行了

再一看数据范围 n106n\le 10^6 ,显然无法通过

那么,我们应该如何来优化呢?

2 换根dp

我们可以利用一些技巧来把这一类问题优化到 Θ(n)\Theta(n) 的时间内解决

接下来,我们就要引入换根dp的一些思想 (或者叫套路) 了

换根dp一般分为三个步骤

1、先指定一个根节点

2、一次dfs统计子树内的节点对当前节点的贡献

3、一次dfs统计父亲节点对当前节点的贡献并合并统计最终答案

光这么说可能不太好理解,我们来结合上面的那个例子看看吧

我们先来看一个节点 uu 的子树里面的节点对它的贡献:

很明显,这个贡献就是子树里所有节点到 uu 的深度和,我们把它记为 gug_u(不过待会会发现根本不需要这个 gug_u

接着,我们记 szusz_u 为以 uu 为根的子树大小,depudep_u 为点 uu11 号节点(我们指定的根节点)的深度(之后有用)

接下来,我们来考虑第2次dfs,也就是计算父亲对它的贡献

我们令 fuf_u 为以 uu 为根节点的深度和

所以,我们令 vvuu 的一个儿子节点,可得 fv=gv+(fu(gv+szv))+(sz1szv)f_v=g_v+(f_u-(g_v+sz_v))+(sz_1-sz_v) )

为啥打上了括号呢?是因为这样更好理解了

如果看不懂,我来解释一下

gvg_v 是以 vv 为根的子树深度和,显然要加上fuf_u 是父亲 uu 节点的答案,显然要减去我们 vv 子树里的信息(不然就多算了)

那么,gv+szvg_v+sz_v 就是我们 vv 子树的信息了

有人就要问了,子树里的的信息不就是 gvg_v 吗?

但是我们是从父亲的答案减去,显然在以 uu 为根时,vv 的子树中的所有节点的深度都有加一,于是就增加 szvsz_v,同理,后面的 sz1szvsz_1-sz_v 就是非 vv 节点子树中的点啦,和上面一样理解一下就好啦qwq

解释完了,我们化简一下这个柿子 fv=fu+sz12×szvf_v=f_u+sz_1-2\times sz_v 发现可以不用记录 gg。于是,我们就可以在 Θ(n)\Theta(n) 的时间内搞定啦

还有什么不懂的地方我们稍微放一点核心代码吧

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问题,通常可以将 Θ(n2)\Theta(n^2) 的暴力统计做到 Θ(n)\Theta(n) 的dp

换根dp一共分成3步:

1、选择一个节点作为根

2、统计子树内节点对它的贡献

3、统计父亲节点对它的贡献并计算最终答案

理解了这些步骤,我们就可以来切做几道题啦

【例题1】Great Cow Gathering

这题和上面那题就非常类似了,只是点和边都加上了权值

理解上面那题这题就不难理解了

【例题2】Nearby Cows

也是一道挺简单的换根dp题

我们令 fu,kf_{u,k} 为以 uu 为根,距离 uukk 的节点权值和

也是先计算 uu 的子树对 uu 的贡献,fu,k=fv,k1f_{u,k}=\sum f_{v,k-1}vvuu 的子节点

接下来,我们计算父亲节点对它的贡献,fu,k=fu,k+ffa,k1f_{u,k}=f_{u,k}+ f_{fa,k-1} ,其中,fafauu 的父亲

最后,我们还要去重 (留给读者自行思考,并不难想)

总时间复杂度 Θ(nk)\Theta(nk)

【例题3】Kamp

一道比较烦的换根dp

分类讨论其实很烦,若要看这题完整的解法,可以看我这篇题解

这里我们令 gug_u 为以 uu 为根的子树中从 uu 开始把所有家在这个子树内的人送回家并回到 uu 节点的最短路程

接下来,由于车子出发之后不一定要回到原来的出发点,所以要统计最长链和次长链,接下来再来一次dfs合并答案即可

【例题4】连珠线

一道比较难的换根dp,也是只提思路

我们令 fu,0/1f_{u,0/1}uu 节点是/不是蓝线中点的答案

接下来,令 gu,0/1,vg_{u,0/1,v}uu 节点是/不是蓝线中点的不考虑 vv 这个儿子的答案

记录一下不考虑每个儿子的最大值,然后合并一下答案就行了,还要看具体的做法可以看题解

4 结尾

换根dp确实是一种难理解的树形dp,但是很多题目都要用到这个技巧,所以必须要学啊