CF1187E Tree Painting

前言

一道挺好的思维题。

本人蒟蒻一枚,这题是本人独立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\n",ans+n);
	
	return 0;
}

  

posted @ 2020-08-05 19:42  魑吻丶殇之玖梦  阅读(165)  评论(0编辑  收藏  举报