CF1324F Maximum White Subtree 题解

博客园同步

原题链接

简要题意:

给定一棵树,每个点有黑白两种颜色;对每个节点,求出包含当前节点的连通图,使得白点数与黑点数差最小。输出这些值。

F题也这么简单,咳咳,要是我也熬夜打上那么一场。。。可惜没时间打啊

美国佬怎么想的,不能让比赛设置成美国的上午,那我们就是下午了;非要设置成下午,那我们就是半夜。。。

首先,这题一看就是 dp \texttt{dp} ,树形 dp \texttt{dp} ,换根 dp \texttt{dp} .

下文中,用 Subtree(i) \texttt{Subtree(i)} 表示 i i 的子树包含的所有节点集合。

father(i) \texttt{father(i)} 表示 i i 节点的父亲。

KaTeX parse error: Expected '}', got '_' at position 15: \texttt{colour_̲i} 表示 i i 节点的颜色值,黑为 1 -1 ,白为 1 1 .

首先,假定 1 1 为根。

f i f_i 表示,当前联通图包含在以 i i 为根的子树内的答案。

则必然存在:

f i = c o l o u r i + x S u b t r e e ( i ) max ( f x , 0 ) f_i = colour_i + \sum_{x \in Subtree(i)} \max(f_x,0)

因为, c o l o u r i colour_i 是必须包含的,其次是所有子树中的答案统计;负数不统计。

下面考虑一个换根(树形) dp \texttt{dp} .用 g i g_i 表示 整棵树去掉以 i i 为根的子树后(保留 i i 节点)的答案。

g i = c o l o u r i + max ( 0 , g father(i) + x S u b t r e e ( father(i) ) i x max ( f x , 0 ) ) g_i = colour_i + \max(0,g_{\texttt{father(i)}} + \sum_{x \in Subtree(\texttt{father(i)})}^{i \not = x} \max(f_x,0))

这也是显然的。

你发现这玩意儿似乎是 O ( n 2 ) O(n^2) 的???

可以,你机智地发现,后面和 f i f_i 的状态转移方程长得不是一点点像!

接着,我们来看后面的部分。

x S u b t r e e ( father(i) ) i x max ( f x , 0 ) = f father(i) max ( 0 , f i ) c o l o u r father(i) \sum_{x \in Subtree(\texttt{father(i)})}^{i \not = x} \max(f_x,0) = f_{\texttt{father(i)}} - \max(0,f_i) - colour_{\texttt{father(i)}}

把这个代入 g g 可知:

g i = c o l o u r i + max ( 0 , g father(i) + f father(i) max ( 0 , f i ) c o l o u r father(i) ) g_i = colour_i + \max(0,g_{\texttt{father(i)}} + f_{\texttt{father(i)}} - \max(0,f_i) - colour_{\texttt{father(i)}})

然后考虑统计答案。

你可能觉得是这样子的:

a n s i = f i + max ( 0 , g i ) ans_i = f_i + \max(0,g_i)

可是你机智的发现,连样例都过不了!!!

什么鬼?

f i f_i g i g_i 都没毛病?

a n s i ans_i 似乎 也没什么问题?

你再次环顾了以下 f f g g 的方程。

你机智的发现,两个函数都计算了 c o l o u r i colour_i .

所以还要减掉一个!

a n s i = f i + max ( 0 , g i c o l o u r i ) ans_i = f_i + \max(0,g_i - colour_i)

天哪,你告诉我这个树形 dp 不会写???

要是 1 1 年前的我,这里肯定是记忆化搜索。

但是,我们就用树形 dp \texttt{dp} 写,怎么地!

具体见代码。

时间复杂度: O ( n ) O(n) .

空间复杂度: O ( n ) O(n) .

实际得分: 100 p t s 100pts .

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+1;

inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

int n,a[N],fa[N],f[N];
int g[N],ans[N];
vector<int>G[N]; 
//正常套路,化树为图,随意求根,形成父亲

inline void solve(int dep,int bs) { //bs 是 dep 的父亲,既方便处理父亲,也为后面的 g 做铺垫
	fa[dep]=bs; f[dep]=a[dep]?1:-1;
	for(int i=0;i<G[dep].size();i++)
		if(G[dep][i]!=bs) {
			solve(G[dep][i],dep);
			if(f[G[dep][i]]>0) f[dep]+=f[G[dep][i]];
		} //加上每个儿子维护的子树值即可,巧妙维护
}

inline void dfs(int dep) {
	int x=fa[dep],t=g[x]+f[x]-(a[x]?1:-1);
	if(f[dep]>0) t-=f[dep];
	if(t<0) t=0; ans[dep]=f[dep]+t;
	g[dep]=t+(a[dep]?1:-1);
	for(int i=0;i<G[dep].size();i++)
		if(G[dep][i]-fa[dep]) dfs(G[dep][i]);
} //维护 g

int main(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<n;i++) {
		int x=read(),y=read();
		G[x].push_back(y);
		G[y].push_back(x);
	}
	solve(1,0); g[1]=a[1]?1:-1; ans[1]=f[1];
	for(int i=0;i<G[1].size();i++) dfs(G[1][i]);
	for(int i=1;i<=n;i++) printf("%d ",ans[i]);
	return 0;
}

发布了61 篇原创文章 · 获赞 86 · 访问量 8902

猜你喜欢

转载自blog.csdn.net/bifanwen/article/details/105368202