题意
给定一棵具有 n n n 个结点,且初始状态全为白色的树,最初可选择任意一个点将其染为黑色,之后的操作只能选择某个黑色点相邻的白色点进行染色。每次操作之后,将得到包含该顶点的白色连通块中结点个数的贡献值,求染黑所有结点后能够得到的最大贡献值是多少。
发现最后的答案值只与起手时选择染黑的第一个点有关,考虑将第一个被染色的点看作根,那么染色过程就是从根节点开始进行 d f s dfs dfs 操作。而每次得到的贡献值其实就是以该节点为根的子树的结点数,由此转化为计数换根 d p dp dp 题(这不是显而易见的吗)。
为了便于操作,接下来所有描述将把结点 1 1 1 作为初始时的根节点。那么对于样例 2 2 2 有以下(见图):
以 c n t cnt cnt 数组记录以某个结点作为根的子树中所有结点数量,则图中表格的意思是:当以 i i i 作为根节点时,编号为 j j j 的点的 c n t cnt cnt 。发现当把某儿子结点提上来作为新的根时,表格中只有原来的根和新的根的 c n t cnt cnt 值将改变。
因此若此时的根为结点 1 1 1 ,且下一时刻将把结点 2 2 2 提为根,则只有 c n t 1 cnt_1 cnt1 和 c n t 2 cnt_2 cnt2 会发生改变。并且, c n t 1 cnt_1 cnt1 原先为 n n n(所有结点的个数), c n t 2 cnt_2 cnt2 将改变为 n n n ,则真正需要关心的是 c n t 1 cnt_1 cnt1 将要改变的值和 c n t 2 cnt_2 cnt2 原先的值。
其中, c n t 2 cnt_2 cnt2 的值是在跑完整棵树的 c n t cnt cnt 时已知的,而 c n t 1 cnt_1 cnt1 的值即为 n − c n t 2 n-cnt_2 n−cnt2 ,可以得到结论:当目前是 1 结点为根,且下一步将把 2 结点作为新的根时,对答案的贡献是: a n s − c n t 2 + n − c n t 2 ans-cnt_2+n-cnt_2 ans−cnt2+n−cnt2 ,将两个对应的 c n t cnt cnt 进行修改后继续跑 d f s dfs dfs 即可。注意还原现场!!
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
vector<int> h[N];
int cnt[N];
int maxx=0,n;
void deal(int u,int fa){
cnt[u]=1;
for(auto x:h[u]){
if(x==fa)
continue;
deal(x,u);
cnt[u]+=cnt[x];
}
}
void dfs(int u,int fa,int ans){
//cout<<u<<" "<<ans<<endl;
for(auto x:h[u]){
if(x==fa)
continue;
int temp1=cnt[u],temp2=cnt[x];
cnt[u]=n-cnt[x],cnt[x]=n;
maxx=max(maxx,ans-temp2+cnt[u]);
dfs(x,u,ans-temp2+cnt[u]);//当前根的原贡献 之前根现在贡献
cnt[u]=temp1,cnt[x]=temp2;
//还原现场
}
}
signed main(){
int u,v;
cin>>n;
for(int i=1;i<n;i++){
cin>>u>>v;
h[u].push_back(v);
h[v].push_back(u);
}
deal(1,0);
for(int i=1;i<=n;i++){
maxx+=cnt[i];//以1为根节点时的答案
}
dfs(1,0,maxx);
cout<<maxx<<endl;
return 0;
}