1. 程式人生 > 實用技巧 >CF1187E Tree Painting 題解

CF1187E Tree Painting 題解

CF1187E Tree Painting 題解

原題面

前置知識: 換根\(DP\)

換根\(DP\)模板題

如果您還不會換根\(DP\)的話,可以先去看看UM巨佬的日報:

#278[UltiMadow] [學習筆記]換根dp

大致題意

給定一棵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);
}