Hile每日算法-3.31-树形dp之换根法

树形dp之换根法

周二周三真的太难了,有早课导致不能熬夜,于是就只能趁着中午的时间写一写,这几天先写点简单的东西,就当重新复习了,应该算是给初学者的知识普及,其他的过了周三再说。

首先来讲一下树的重心

树的重心,即 树上到所有点的距离之和最小/以此为根深度最小/最大子树大小最小 的点,具有很多方便的性质,如:

1.当一棵树添加/删除一个节点,树的重心最多移动一个位置。(动态维护)(19icpc徐州M题,so~no~chi~no~sa~da~me~)

2.当两棵树通过某点连接在一起形成新树时,新树的重心一定在连接两棵旧树重心的路径上。(两树合并)

3.以一颗树的重心为根,划分的子树大小一定不超过原树的一半。(树分治)

众所周知,回字有四种写法,门前有两棵枣树,重心也有两种求法(通常情况下),一种是两遍dfs找树上最长路径的中点,一种就是今天要讲的内容——树形dp之换根法。

首先,初始是不知道重心所在的,最直接的方法就是枚举每个点作为重心的情况。由上面重心的定义可以知道,重心一定是最大的子树大小最小的点。因此,我们不妨先把11作为根,设s[i]s[i]为以11为根时子树ii的大小,根据dfs的性质,子树遍历结束的时候,其s[i]s[i]也被更新完了,所以,一次从11开始的dfs就可以求出所有点的子树大小。

如果我们用f[i]f[i]来表示以ii为根的最大子树大小,根据上述算法,dfs结束后可以求出f[1]=max(s[son])f[1]=max(s[son])。此时,如果根从11换到了11的子节点上,f[i]f[i]该如何变化呢?

f[i]=max(s[son],ns[i])f[i]=max(s[son],n-s[i])

这里满足了动态规划的三要素:状态边界条件转移方程

更具体地说,要在树上所有的点中找到满足条件的根,就要确定随根变化的数据(状态)、初始当根为11的情况(边界条件)、根从上往下移动的变化(转移方程)。

来一道题感受一下:

CF1187E Tree Painting

题意:给定一棵所有结点初始为白色的树,第一回合选择任意一个白点涂黑,接下来每次都选和黑点邻接的白点涂黑,每次(包括第一次)选点时会获得这个点所在的白点连通块大小的分数,求可能获得的最大分数。

这就是换根法的直接应用了。第一个涂黑的点就相当于选一个根,然后沿着根往下走。假设以11为根,s[i]s[i]表示ii的子树大小,11为根时的答案ans1ans_1即为i=1ns[i]\sum_{i=1}^ns[i]。当根由uu变为vv时,ansv=ansus[v]+(s[1]s[v])=ansu2s[v]s[1]ans_v=ans_u-s[v]+(s[1]-s[v])=ans_u-2*s[v]-s[1]ansians_i的最大值就是答案。

#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到了作业也没做,导致这次写的比较仓促,内容也不是很详尽,还好算法并不复杂。明、后两天准备写一写数论基础,尤其是数学角度的推理过程(好像我也不怎么擅长这些)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章