简介
换根 DP 是树形 DP 的一个分支,用于解决根节点不确定的问题,可以把 O ( n ) O(n) O(n) 枚举根节点的过程通过两次 dfs 优化掉,(据说)是一种重要的技巧。
分析
以一道经典例题 [POI2008]STA-Station 为例。
题意非常明了了,不解释了。
正如一般换根 DP 学习笔记的套路,我们先想想怎么 O ( n 2 ) O(n^2) O(n2) 的解决这个问题,非常简单,我们可以枚举一个根,然后 O ( n ) O(n) O(n) dfs 查询深度最后取 max \max max。
显然 O ( n 2 ) O(n^2) O(n2) 是通不过这个题的,那么应该怎样去优化呢?
一个不太好想到的想法是,如果知道了一个节点的答案,不难算出其子节点的答案。
这里直接给出换根 DP 做法,我们随便拉一个点出来,记为 u u u。现在我们钦定它就是根。
一次 dfs,算出答案,记为 f u f_u fu。同时我们做如下约定: w i w_i wi 指 i i i 号点的重量(以 u u u 为整棵树的根中以 i i i 为根子树大小)。
考虑如果我们把根从 u u u 换成它的儿子节点 v v v ,会发生什么事。
左边是换根之前的示意图,右边是换根之后。
有图有真相,以 v v v 为根的子树深度整体减少了 1 1 1,其余的部分由于全是 u u u 的子树(新树中),所以深度随着 u u u 增加了 1 1 1。
形式化的: f v = f u − w v + ( n − w v ) = f u + n − 2 w v f_v=f_u-w_v+(n-w_v)=f_u+n-2w_v fv=fu−wv+(n−wv)=fu+n−2wv
于是乎,我们成功的把问题转化为了两次 dfs,这就是换根 DP。
最后放下例题代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
int x=0,k=1; char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')k=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*k;
}
int head[1000005],depth[1000005],cnt,n,m,s,f[1000005],size[1000005],ans=0;
struct Edge{
int u,v,next;
}e[2000005];
inline void add(int u,int v){
e[++cnt].u=u;
e[cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void dp1(int u,int fa){
size[u]=1;
for(int j=head[u];j;j=e[j].next){
int v=e[j].v;
if(v!=fa){
depth[v]=depth[fa]+1;
dp1(v,u);
size[u]+=size[v];
}
}
}
void dp2(int u,int fa){
for(int j=head[u];j;j=e[j].next){
int v=e[j].v;
if(v!=fa){
f[v]=f[u]-2*size[v]+size[1];
dp2(v,u);
}
}
}
signed main(){
cin>>n;
for(int i=1,u,v;i<=n-1;i++){
cin>>u>>v;
add(u,v);
add(v,u);
}
dp1(1,-1);
for(int i=1;i<=n;i++){
f[1]+=depth[i];
}
dp2(1,-1);
int ans=0,id=114514;
for(int i=1;i<=n;i++){
if(ans<f[i]){
ans=f[i];
id=i;
}
}
cout<<id<<endl;
return 0;
}
(码风不一样是吧,这是早期贺题作品)
例题
HDU5834 Magic boy Bi Luo with his excited tree
题意简述:给定一棵 n n n 个节点的树, 每个节点都有价值 a i a_i ai 每条边有花费 c i c_i ci,每经过一个点,就可以得到这个 a i a_i ai,但经过一条边会花费 c i c_i ci,并且价值只能得到一次但每次经过都要花费 c i c_i ci,求从每个节点出发可以得到最大利益。
保存了,明天写。