树形dp之换根法
周二周三真的太难了,有早课导致不能熬夜,于是就只能趁着中午的时间写一写,这几天先写点简单的东西,就当重新复习了,应该算是给初学者的知识普及,其他的过了周三再说。
首先来讲一下树的重心。
树的重心,即 树上到所有点的距离之和最小/以此为根深度最小/最大子树大小最小 的点,具有很多方便的性质,如:
1.当一棵树添加/删除一个节点,树的重心最多移动一个位置。(动态维护)(19icpc徐州M题,so~no~chi~no~sa~da~me~)
2.当两棵树通过某点连接在一起形成新树时,新树的重心一定在连接两棵旧树重心的路径上。(两树合并)
3.以一颗树的重心为根,划分的子树大小一定不超过原树的一半。(树分治)
众所周知,回字有四种写法,门前有两棵枣树,重心也有两种求法(通常情况下),一种是两遍dfs找树上最长路径的中点,一种就是今天要讲的内容——树形dp之换根法。
首先,初始是不知道重心所在的,最直接的方法就是枚举每个点作为重心的情况。由上面重心的定义可以知道,重心一定是最大的子树大小最小的点。因此,我们不妨先把作为根,设为以为根时子树的大小,根据dfs的性质,子树遍历结束的时候,其也被更新完了,所以,一次从开始的dfs就可以求出所有点的子树大小。
如果我们用来表示以为根的最大子树大小,根据上述算法,dfs结束后可以求出。此时,如果根从换到了的子节点上,该如何变化呢?
这里满足了动态规划的三要素:状态、边界条件、转移方程。
更具体地说,要在树上所有的点中找到满足条件的根,就要确定随根变化的数据(状态)、初始当根为的情况(边界条件)、根从上往下移动的变化(转移方程)。
来一道题感受一下:
题意:给定一棵所有结点初始为白色的树,第一回合选择任意一个白点涂黑,接下来每次都选和黑点邻接的白点涂黑,每次(包括第一次)选点时会获得这个点所在的白点连通块大小的分数,求可能获得的最大分数。
这就是换根法的直接应用了。第一个涂黑的点就相当于选一个根,然后沿着根往下走。假设以为根,表示的子树大小,为根时的答案即为。当根由变为时,。的最大值就是答案。
#include <bits/stdc++.h>
#define N 200010
#define ll long long
using namespace std;
int n;
vector<int> G[N];
ll sum[N],ans,s;
void dfs1(int u,int fa)
{
for(int v:G[u])
if(v!=fa)
{
dfs1(v,u);
sum[u]=sum[v]+1;
}
s+=sum[u];
}
void dfs2(int u,int fa,ll s)
{
ans=max(ans,s);
for(int v:G[u])
if(v!=fa)
dfs2(v,u,s-2LL*sum[v]+sum[1]);
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1,u,v;i<n;i++)
{
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs1(1,0);
dfs2(1,0,s);
cout<<ans;
}
睡眠时间不够,ddl到了作业也没做,导致这次写的比较仓促,内容也不是很详尽,还好算法并不复杂。明、后两天准备写一写数论基础,尤其是数学角度的推理过程(好像我也不怎么擅长这些)。