前言
一道挺好的思维题。
本人蒟蒻一枚,这题是本人独立A掉的第一道洛谷蓝色难度的CF思维题,极具纪念意义。故本人会将思考过程尽量全面地记录下来,以观察思考的不足之处。
题意简述
给定一棵树,初始时全为白点,要求按以下方法进行n次染色操作:
1.第一次可以任意选择一个节点染成黑色。
2.以后每一次任意选择一个与黑点有直接边相连的白点,将其染成黑色。
定义每次染色可获得的权值为该次染色时,所染的白点所在连通块的大小。
要求n次染色后所获得的权值之和最大,求这个最大值。
算法概述
最优化问题,优先考虑DP。
首先很容易发现一个很显然的性质:当第一次染色的节点确定下来之后,以后染色所能获得的权值之和就确定了。
暴力枚举第一次染色的节点显然不行,所以可以考虑用dp来计算这个权值之和。
f[u]表示以u为一号染色点时所能获得的权值之和。
我们关键来看这个f[u]如何计算。不难发现f[u]可分成两部分:
①从u出发向以u为根的子树染色,所能获得的权值之和。
②从u出发向u的父亲方向染色,所能获得的权值之和。
子树的信息还是比较方便统计的,主要难点在于第②部分,首先显然u也是在其父节点的子树当中的,所以我们发现一个点的子树信息可能要重复被用到,故我们再开一个数组。
dp[u]表示从u出发向以u为根的子树染色,所能获得的权值之和。要计算dp[u],只需考虑其每个儿子即可。
显然dp[u]=∑(dp[v]+siz[v]),其中v为u的儿子节点,siz[u]表示以节点u为根的子树大小。
所以我们可以自底向上计算出每个点的dp值。
那么f[u]的第①部分就是dp[u]了。
再来看第②部分,记u的父节点为fa,考虑f[fa]的组成:一部分是向u染色,另一部分是向其他节点染色。
所以我们可以在f[fa]中挖掉向u染色的部分,然后再加上从u到fa这一步的值(即将fa这个节点染色的权值)即可。形式化来说,挖掉向u染色的部分即f[fa]-dp[u]-siz[u],再加上从u到fa这一步的值即n-siz[u](总节点数减去子树u的大小)。
故第②部分的值就等于f[fa]-dp[u]+n-2*siz[u]。
于是我们就得到了f的状态转移方程:f[u]=dp[u]+f[fa]-dp[u]+n-2*siz[u]=f[fa]+n-2*siz[u]。
所以我们可以自上往下计算出每个点的f值。
最后再比较得出全局最大值即可。
当然,由于我们计算f[u]时并未加上第一步染色u点时的权值,故而最后还应将答案加上n。
时间复杂度O(n+m)。
根据如上分析,我们发现了最开始思考时的思维漏洞——dp数组其实根本不需要。
我们只需要先预处理出以1为根时的权值之和,然后以此为边界,根据上面的状态转移方程再在dfs过程中进行递推即可。
然后我们重新考虑一下f[u]的计算:
考虑f[fa]的组成:(1)一部分是向u染色,(2)另一部分是向其他节点染色。
考虑f[u]的组成:(3)一部分是向fa染色,(4)另一部分是向子树染色。
我们发现(4)是包含在(1)中的,(4)=(1)-siz[u],而(2)是包含在(3)中的,(3)=(2)+n-siz[u]。
两式一加即可直接得出结果。
最后这题答案比较大,可能会爆int,故需要开long long。
参考代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int N=2e5+10; struct Edge{ int to,next; }edge[N<<1];int idx; int h[N]; void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;} ll f[N]; int siz[N]; int n; ll dfs1(int p,int fa) { ll res=0; //res的值即为dp值 siz[p]=1; for(int i=h[p];~i;i=edge[i].next) { int to=edge[i].to; if(to==fa)continue; res+=dfs1(to,p)+siz[to]; siz[p]+=siz[to]; } return res; } void dfs2(int p,int fa) { for(int i=h[p];~i;i=edge[i].next) { int to=edge[i].to; if(to==fa)continue; f[to]=f[p]+n-2*siz[to]; dfs2(to,p); } } int main() { memset(h,-1,sizeof h); scanf("%d",&n); for(int i=1;i<=n-1;i++) { int u,v; scanf("%d%d",&u,&v); add_edge(u,v); add_edge(v,u); } f[1]=dfs1(1,0); dfs2(1,0); ll ans=0; for(int i=1;i<=n;i++)ans=max(ans,f[i]); printf("%lld ",ans+n); return 0; }