CF1187E Tree Painting 题解
原题面
前置知识: 换根(DP)
换根(DP)模板题
如果您还不会换根(DP)的话,可以先去看看UM巨佬的日报:
大致题意
给定一棵n个点的树 初始全是白点
要求你做n步操作,每一次选定一个与一个黑点相隔一条边的白点,将它染成黑点,然后获得该白点被染色前所在的白色联通块大小的权值。
求可获得的最大权值
分析
几乎是一道裸的模板题了...
和P3478几乎一摸一样,只是需要一个微小的结论
PS:图中节点的编号有一点微小的错误,不过并不影响阅读
假如说我们选了图中的1号节点作为第一个涂色的点(图中蓝色的点)
那下一个涂色的节点肯定就能选择它的几个儿子了(图中深红色的点)
同时,由于父亲节点已经被涂色了,其子节点不可能再和上面的"祖先"辈节点有联通了
能对其产生贡献的只有自己的子树
因此当一个父亲节点被涂色后,其所有子树都是相对"独立"的,涂色顺序的变化对总贡献值无任何影响
故当第一个节点被涂色后,剩下节点的涂色顺序均无法对总贡献值产生影响
代码实现
单纯的暴力枚举每个根的位置的话照这个数据范围肯定会T飞
考虑换根DP
应该很容易状态转移方程推出:
- (dp[v] = dp[u]-2size[v]+size[1])
具体这个方程怎么来的,我之前写的P3478的题解跟前面UM巨佬的日报里也有讲
套上换根(DP)的板子即可
贴上丑陋的代码:(其实只要把P3478的代码改一行就可以了)
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200010;
vector <int> son[MAXN];
int vis[MAXN],n;
long long size[MAXN];
long long f[MAXN];
void dfs(int u){
size[u] = 1;
vis[u] = 1;
for(int i=0;i<son[u].size();i++){
int v = son[u][i];
if(!vis[v]){
dfs(v);
size[u]+=size[v];
}
}
}
void dp(int u){
vis[u] = 1;
for(int i=0;i<son[u].size();i++){
int v = son[u][i];
if(!vis[v]){
f[v] = f[u] + size[1] - 2*size[v];
dp(v);
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
son[u].push_back(v);
son[v].push_back(u);
}
dfs(1);
for(int i=1;i<=n;i++){
f[1]+=size[i];
}
memset(vis,0,sizeof(vis));
dp(1);
long long ans = -0x3f;
for(int i=1;i<=n;i++){
ans = max(ans , f[i]);
}
printf("%lld",ans);
}