前言
一道挺好的思维题。
本人蒟蒻一枚,这题是本人独立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;
}