1.换根dp
换根
d
p
dp
dp其实是树形
d
p
dp
dp的一个分支,通常用来在线性或亚线性的复杂度里解决树上定点的问题
刚开始学习的时候可能会不太好理解,但是做多题之后就好了
2.引入
现在有一个这样的问题
给你一棵树,要找一个点 p p p,使得当 p p p为根的时候,所有节点的深度之和最大
首先考虑暴力怎么做,就是暴力枚举
p
p
p,然后
Θ
(
n
)
\Theta(n)
Θ(n)计算答案,最后总复杂度是
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)
是不是有点慢?这题的范围是
n
≤
1
0
6
n\leq 10^6
n≤106,显然挂掉了
那么我们应该怎么进行优化呢?
就需要用到换根
d
p
dp
dp的做法
3.做法流程
换根 d p dp dp通常需要两次 d f s dfs dfs来实现,第一次 d f s dfs dfs时,我们先随便指定一个点为根(通常为 1 1 1),但是不同的题有特例
我们在第一次 d f s dfs dfs的时候处理出子树中的一些信息
但是因为我们发现影响答案的不一定只在子树内部,在另一部分也是可以的,所以我们需要第二次 d f s dfs dfs统计不在子树中的答案情况,但是这一部分的答案我们发现其实是可以利用他到根之间的子树的答案推出来
然后合并答案就可以
听起来云里雾里?接下来我们回到例题中
先考虑子树中的信息,我们用 f i f_i fi表示以 i i i为根节点的子树中,所有点的深度之和
那么显然我们会得到这样的一个转移方程 f u = ∑ f v f_u=\sum f_v fu=∑fv
然后在我们进行第二次 d f s dfs dfs时,我们访问到哪个节点,就令他为根
当根从 u u u“换到” v v v时,我们发现原来所有的在 v v v这棵子树的点的深度都 − 1 -1 −1,相应的,其他的不在 v v v这棵子树的点的深度都会 + 1 +1 +1
这句话可能不太好理解,但是这也是换根 d p dp dp的难点。换根 d p dp dp的根本思想就是把一个在变的东西变成我们知道的东西,相当于一个动态转成静态的过程
如果不太理解的话可以手画一画,但是应该还好,毕竟这可以算换根 d p dp dp最简单的题了
也就是说,如果我们用 g i g_i gi表示以 i i i为根的时候的答案的情况,那么转移方程就是
g v = g u − s i z v + ( n − s i z v ) g_v=g_u-siz_v+(n-siz_v) gv=gu−sizv+(n−sizv)
然后我们最后找 g g g的 max \max max就好了
那么这样的话经过两次 d f s dfs dfs,我们最开始的问题就转化成一个复杂度 Θ ( n ) \Theta(n) Θ(n)的了
4.程序架构
换根 d p dp dp其实有一个半模板的东西,就是基本上所有的程序都长得差不多,只是维护的不同而已
每个人可能有自己的写法,这里给出一个我自己喜欢的写法的这道题的伪代码伪代码
function dfs1(u,fa)
begin
//初始化变量
siz[u]<-1
f[u]<-dep
for v in son{u}
if v=fa continue
else
dfs1(v,u)
//更新子树中的答案
siz[u]<-siz[u]+siz[v]
f[u]<-f[u]+f[v]
end
function dfs2(u,fa)
begin
//根据fa的答案来更新u的答案
g[u]<-g[fa]-siz[u]+(n-siz[u])
for v in son{u}
if v=fa continue
else
dfs2(v,u)
end
in main
readall
dfs1(1,0)
dfs2(1,0)
for i=1->n
if f[i]+g[i]>max
max=f[i]+g[i],ans=i
print ans
return
5.例题分析
我们前面解决一个简单的换根 d p dp dp问题,但是显然丧心病狂的出题人不可能只让你维护一个那么好维护的 s i z siz siz
P3047 [USACO12FEB]Nearby Cows G
看到数据范围应该很容易猜到这题复杂度应该是 Θ ( n k ) \Theta(nk) Θ(nk)
我们用 f i , j f_{i,j} fi,j表示以 i i i为根节点的子树中距离 i i i为 j j j的数量
然后随便转移一下 f u , j = f v , j − 1 f_{u,j}=f_{v,j-1} fu,j=fv,j−1
接下来考虑换根,因为和他距离 ≤ k \leq k ≤k的点不可能只在下面
用 g u , k g_{u,k} gu,k表示不在 u u u的子树中的距离他为 j j j的数量
向下走的时候,首先会有他爸爸再往上的,但是也有可能有他爸爸的儿子,这个时候我们需要减掉 u u u的贡献(就是这个东西让换根 d p dp dp变得特别恶心),也就是说
g u , j = g f a , j − 1 + f f a , j − 1 − f f a , j − 2 g_{u,j}=g_{fa,j-1}+f_{fa,j-1}-f_{fa,j-2} gu,j=gfa,j−1+ffa,j−1−ffa,j−2
然后还有一些细节,这里不具体说了
P2986 [USACO10MAR]Great Cow Gathering G
跟最开始说的例题差不多,只是加上了边权点权,如果理解了上面那个这个应该不难
就不说了
这是最近CF一场 d i v 3 div3 div3的最后一题,当时我还没有接触到换根 d p dp dp的时候自己yy了一个换根 d p dp dp的做法,居然还给A掉了,后来才知道这是换根 d p dp dp
首先考虑子树里面怎么维护,我们用 f i f_i fi表示 i i i为根节点子树中黑白差最大值
转移就是 f u = ∑ max ( f v , 0 ) f_u=\sum \max(f_v,0) fu=∑max(fv,0)
然后我们考虑换根,用 g i g_i gi表示 i i i点的答案,那么我们就得到了一个这样的的转移
g u = { max ( g f a , f u ) , f u ≥ 0 max ( g f a , c o l o r u ) , f u < 0 g_u= \begin{cases} \max(g_{fa},f_u),f_u\geq 0 \\ \max(g_{fa},color_u),f_u< 0 \end{cases} gu={max(gfa,fu),fu≥0max(gfa,coloru),fu<0
最后输出 g u g_u gu就可以了
我们用 f i f_i fi表示以 i i i为根节点的子树中,从 i i i出发又回到 i i i并且把所有人送回去的路程,当然是最短的,那么显然的,有一个这样的转移方程
f u = { f v + 2 × dis ( u , v ) , s i z v > 0 0 , s i z v = 0 f_u= \begin{cases} f_v+2\times \operatorname{dis}(u,v),siz_v> 0\\ 0,siz_v=0 \end{cases} fu={fv+2×dis(u,v),sizv>00,sizv=0
但是这道题恶心的地方就在于,我们最后车不一定要回到起点,那么一个显然的贪心策略是我们最后送离起点最远的那个点,然后就不回来了,所以最后答案就是 f u f_u fu减去以 u u u为起点的最长链(当然链尾是一个有人的点)
但是根据换根dp的套路,我们还需要维护一个次长链,为什么呢?我们下面在换根的时候说
也就是说,我们第一次 d f s dfs dfs需要维护四个东西
-
s i z i siz_i sizi表示以 i i i为根节点的子树的大小
-
f i f_i fi表示以 i i i为根节点的子树中,从 i i i出发又回到 i i i并且把所有人送回去的路程
-
d i s 1 i dis1_i dis1i,最长链
-
d i s 2 i dis2_i dis2i,不在最长链所在子树中的次长链
换根的时候,我们用 g i g_i gi表示不在 i i i子树里面的的答案情况
考虑进行分类讨论,以样例为例
在以 1 1 1为根的时候,当我们 d f s dfs dfs到 2 2 2的时候,我们需要包含的应该是 1 1 1往上的部分,也就是 g 1 g_1 g1,同时应该包含除了 2 2 2这棵子树以外的答案部分,也就是 f 1 − f 2 f_1-f_2 f1−f2,但是这还不够,我们发现, f 1 − f 2 f_1-f_2 f1−f2之后我们仍然包含 2 × dis ( 1 , 2 ) 2\times \operatorname{dis}(1,2) 2×dis(1,2)这一部分的答案,当然如果下面还有点可以不管,但是如果我们访问到 5 5 5时,实际上 ( 2 , 5 ) (2,5) (2,5)这条边我们是不用走的,所以我们需要单独判断一下
那么 g g g的转移方程就是
g u = g f a + f f a − f u + 2 × dis ( u , f a ) × ( s i z u > 0 ) g_u=g_{fa}+f_{fa}-f_u+2\times\operatorname{dis}(u,fa)\times(siz_u>0) gu=gfa+ffa−fu+2×dis(u,fa)×(sizu>0)
但是同时我们的最长链也有可能从上面更新过来对吧,所以我们再搞一个 u p up up数组表示他往上走的最长链
更新 u p up up的时候就需要用到我们之前维护的 d i s 2 dis2 dis2了,因为如果最长的在 u u u这棵子树里面,我们就需要 d i s 2 dis2 dis2, u p up up的转移方程就很简单了
u p u = { max { u p f a , d i s 1 f a } + dis ( u , f a ) , d i s 1 f a − d i s 1 u ≠ dis ( u , f a ) max { u p f a , d i s 2 f a } + dis ( u , f a ) , d i s 1 f a − d i s 1 u = dis ( u , f a ) up_u= \begin{cases} \max\{up_{fa},dis1_{fa}\}+\operatorname{dis}(u,fa),dis1_{fa}-dis1_u\neq\operatorname{dis}(u,fa) \\ \max\{up_{fa},dis2_{fa}\}+\operatorname{dis}(u,fa),dis1_{fa}-dis1_u=\operatorname{dis}(u,fa) \end{cases} upu={max{upfa,dis1fa}+dis(u,fa),dis1fa−dis1u=dis(u,fa)max{upfa,dis2fa}+dis(u,fa),dis1fa−dis1u=dis(u,fa)
那么对于节点 i i i,我们最后的答案就是 f i + g i − max { d i s 1 i , u p i } f_i+g_i-\max\{dis1_i,up_i\} fi+gi−max{dis1i,upi}
这道题比较特殊,我们前面说换根 d p dp dp问题通常是随便选点当根,但是这道题有点不同(当然不是随便选就不能做了),我们可以选择重心最为最开始的根以避开复杂的分类讨论
我们发现,一个点最多只会有一个子树的大小超过 n 2 \frac{n}{2} 2n,我们只要能拆下来一部分,接到这个点下面显然是更优的,而且我们尽量拆的多,只要不超过 n 2 \frac{n}{2} 2n就可以。因为只让砍一刀,所以切下来的肯定还是一棵子树。所以我们需要维护的是这个点为根时所有子树大小不超过 n 2 \frac{n}{2} 2n的最大值即可
但是在固定根时,最大的部分有可能在下面也有可能在上面,所以我们固定根为重心,这样答案一定从上面转移过来
但是注意一棵树最多有两个重心,所以我们要让两个重心分别为根计算答案
显然我们维护 f i f_i fi为最大的子树, g i g_i gi为次大的子树
那么怎么计算能够切下来的最大的大小不超过 n 2 \frac{n}{2} 2n的子树这个东西 h i h_i hi呢?
我们可以列出这样一个转移方程
h v = { max ( h u , f i ) , s i z v ≠ f i max ( h u , g i ) , s i z v = f i h_v= \begin{cases} \max(h_u,f_i),siz_v\neq f_i \\ \max(h_u,g_i),siz_v=f_i \end{cases} hv={max(hu,fi),sizv=fimax(hu,gi),sizv=fi
然后对于每一个点 i i i,如果 n − s i z i − h i ≤ n 2 n-siz_i-h_i\leq \frac{n}{2} n−sizi−hi≤2n,就是 t r u e true true,否则是 f a l s e false false