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

这篇博客介绍了树的重心概念及其性质,重点讲解了树形dp中的换根法,用于寻找最大子树大小最小的点。文章通过动态规划的状态、边界条件和转移方程解释了如何使用换根法,并结合CF1187E Tree Painting问题进行了实例说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树形dp之换根法

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

首先来讲一下树的重心

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

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

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

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

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

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

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

f [ i ] = m a x ( s [ s o n ] , n − s [ i ] ) f[i]=max(s[son],n-s[i]) f[i]=max(s[son],ns[i])

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

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

来一道题感受一下:

CF1187E Tree Painting

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值