CodeForces - 1324F Maximum White Subtree(树形dp)

题目链接:点击查看

题目大意:给出 n 个点组成的树,每个点都有一个颜色,非黑即白,现在问对于每个点而言,选出一个连通块,使得白色点的个数与黑色点的个数做差最大

题目分析:记录一下div3的第一次ak,实际上应该是伪ak,感谢zx学长最后抬了我一手F题,dp真的是天克我,不过通过这个题真的学到了不少

首先这个题,题意给出的subtree一定别想当然以为是子树,我一开始就是因为这里读错题然后就被卡懵了,这个题中的subtree其实是子连通块的意思,知道了子连通块之后,我们就可以两次树形dp来做了,因为我们要使得 cnt_white - cnt_black 尽可能大,那么每个节点如果颜色为白色,其贡献就是 1 ,否则就是 -1 ,这样问题就转换为了两次 dp 求出最大子连通块的权值和,一次是自底向上,一次自顶向下,自底向上的话我们可以直接维护dp[ i ],表示以点 i 为根的子树中的最大连通块,注意这个子树是有方向的,这也就意味着我们需要自顶向下再来一次,这一次我们主要是为了处理自底向上过程中无法包含的 fa - u 这条树链所代表的子树,第二次dfs的时候我们就可以直接维护答案了,这一次我们从上向下走的时候,需要额外维护一个sum变量,用于储存 fa - u 这条链所代表的子树的贡献,显然 sum 最小为 0,代表的意义就是不选 fa - u 这条链,此时的答案也就是 ans[ u ] = dp[ u ] + sum 了,现在的问题转换为了如何向下传递 sum ,其实无非只有两种情况:

  1. ans[ u ]大于 0,可以向子树传递贡献
  2. ans[ u ]小于等于 0,无法向子树传递贡献

为什么要讨论ans[ u ]呢,因为ans[ u ]此时已经完成了自底向上+自顶向下的更新了,也就是说ans[ u ]代表的就是包含 u 在内的子连通块的最大权值了,所以ans[ u ]的正负,就决定着能否对相邻的子节点做出贡献了,如果ans[ u ]为正的话,那么减去 dp[ v ] 就是 fa - u 这条链代表的子树的权值了

最后代码实现起来还是比较好写的了

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
      
typedef long long LL;

typedef unsigned long long ull;
      
const int inf=0x3f3f3f3f;
 
const int N=2e5+100;

int a[N],dp[N],ans[N];

vectornode[N];

void dfs1(int u,int fa)//自底向上
{
	dp[u]=a[u];
	for(auto v:node[u])
	{
		if(v==fa)
			continue;
		dfs1(v,u);
		dp[u]+=max(dp[v],0);
	}
}

void dfs2(int u,int fa,int sum)//自顶向下
{
	ans[u]=dp[u]+sum;
	for(auto v:node[u])
	{
		if(v==fa)
			continue;
		dfs2(v,u,max(0,ans[u]-max(0,dp[v])));
	}
}

int main()
{
#ifndef ONLINE_JUDGE
//    freopen("input.txt","r",stdin);
//    freopen("output.txt","w",stdout);
#endif
//    ios::sync_with_stdio(false);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",a+i);
		if(a[i]==0)
			a[i]=-1;
	}
	for(int i=1;i

 

你可能感兴趣的:(CodeForces上分,树形dp)