CF1187E Tree Painting【换根dp】
题意
一棵$N$个节点的树,初始时所有的节点都是白色,第一次可以选择任意一个把它涂成黑色。接下来,只能把与黑色节点原来相连的白色节点涂成黑色(涂成黑色的点视为被删去,与其它节点不相连)。每一次涂的分数为涂的这个白色节点所在的联通块里的白色节点的个数。要把这$N$个节点都涂成黑色,求能获得的最大分数。
(人工翻译+手打qwq,若有误差请告诉我)
分析
没有什么太大思路的题先乱搞
然后我们发现 我们在最开始随便把这棵树的其中一个节点涂成黑色之后,这个树的涂法就唯一确定了。涂了之后这棵树就会散成很多棵小树,如果我们把这棵树提起来,把最开始涂的那个点视作根,这棵树就变成了根的子树。而每次只能涂与黑色节点原来相连的白色节点,也就是只能涂原来根的那些儿子。而且根的儿子被涂的顺序不会影响答案(他们已经变成了不相连的联通块,先涂哪一个联通块是不会影响答案的),所以答案就确定了。
也就是说,我们从根开始涂,选取的根不同,答案就不同。那么我们将所有的节点都当作一次根,然后模拟一次,取最大值,就可以了。
但是这样的复杂度太高。
让我们先分析一下这个模拟的过程。
我们来观察这个节点:9
它被计算的情况有:删去1时,删去6时,删去7时,删去9时
也就是它的每一个爸爸或者自己被删去的时候,它都要被算入答案
所以它对答案的贡献就是它的爸爸的个数+1=深度
每个节点对答案的贡献都为它的深度
答案就是所有节点的深度和
再来考虑换根的问题
还是这张图
当根从1换到6时,6,7,8,9(也就是6的子树)的深度都要-1,而其它节点的深度都+1
所以把根从爸爸换到儿子,答案会少$size[son]$而会多$(n-size[son])$,所以我们再遍历一下这颗树,把根从爸爸换到儿子那里去,然后再计算答案,取最大值。
(2019.11.5二刷此题,重写代码)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 #include<queue> 6 using namespace std; 7 #define N 200005 8 #define INF 0x3f3f3f3f 9 #define ll long long 10 int rd() 11 { 12 int f=1,x=0;char c=getchar(); 13 while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();} 14 while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();} 15 return f*x; 16 } 17 int n; 18 vector<int>G[N]; 19 int siz[N],d[N]; 20 ll ans,res; 21 void dfs(int u,int f,int dep) 22 { 23 d[u]=dep; 24 siz[u]=1; 25 for(int i=0;i<G[u].size();i++) 26 { 27 int v=G[u][i]; 28 if(v==f) continue; 29 dfs(v,u,dep+1); 30 siz[u]+=siz[v]; 31 } 32 res+=d[u];//以点1为根的答案(1号点工具人 33 } 34 void dfs2(int u,int f) 35 { 36 ans=max(ans,res); 37 for(int i=0;i<G[u].size();i++) 38 { 39 int v=G[u][i]; 40 if(f==v) continue; 41 res-=siz[v]; 42 res+=(n-siz[v]); 43 dfs2(v,u); 44 res-=(n-siz[v]); 45 res+=siz[v];//回溯 也可以传成参数 46 } 47 } 48 int main() 49 { 50 n=rd(); 51 for(int i=1;i<n;i++) 52 { 53 int u=rd(),v=rd(); 54 G[u].push_back(v); 55 G[v].push_back(u); 56 } 57 dfs(1,0,1); 58 dfs2(1,0); 59 printf("%lld\n",ans); 60 return 0; 61 }