1. 程式人生 > 實用技巧 >P3478 [POI2008]STA-Station題解

P3478 [POI2008]STA-Station題解

P3478 [POI2008]STA-Station題解

原題面

知識點

  • 換根DP

大致題意

給出一個 N 個點的樹,找出一個點來,以這個點為根的樹時,所有點的深度之和最大

分析

換根DP的模板題。

如果您還不會換根DP的話,可以先去看看UltiMadow巨佬的文章:

【日報#278】[學習筆記]換根dp,我一開始也是從那裡學的qwq

這裡我們設

  • \(size[i]\)為以\(1\)為根節點時節點\(i\)的子樹大小

  • \(dep[i]\)為以\(1\)為根節點時節點\(i\)的深度大小

  • \(dp[i]\)為以\(i\)為根節點時深度之和的大小

很明顯,我們可以通過一遍DFS求出以\(1\)

為根節點時的深度之和

如果一個個的去算的話

照這個資料範圍,顯然會T飛

這個時候就要用到換根DP了

換根\(DP\)優化

可以看出,當我們把根節點從1換到3時

對子節點3的貢獻由兩部分組成

1.自己子樹的貢獻(圖中的k)

2.父親節點\(1\)的貢獻


如何轉移

  • 首先是\(k\),作為自己子樹所產生的貢獻肯定要加上

  • \(dp[u]\)為以\(u\)為根節點時的深度總值,在計算時,要減去\(v\)的子樹所產生的貢獻,不然就重複計算了,同時

在以 \(u\)為根時,v節點及其子樹內的所有節點的深度都增加了\(1\),需要減去

(圖中紅色的節點)

合起來就是\(dp[u]-(size[v]+k)\)

  • 除v子樹外的其他節點也一樣

在以\(v\)為根時,除\(v\)節點及其子樹外的其他節點的深度都增加了\(1\)

(圖中藍色的節點)

合起來就是\((size[1]-size[v])\)

得到轉移方程

  • \(dp[v] = k+(dp[u]-(k+size[v]))+(size[1]-size[v])\)

化簡一下

  • \(dp[v] = dp[u]-2size[v]+size[1]\)

轉移方程推出來了,程式碼部分就不難實現了,兩遍dfs,一次dfs統計子樹內的節點對當前節點的貢獻
一次dfs換根

貼個程式碼:

#include<bits/stdc++.h>
using namespace std;
const int MAXN  = 100010;
long long dp[MAXN],dep[MAXN],size[MAXN];
int vis[MAXN];
vector <int> son[MAXN];
int n;

void dfs1(int x){
	size[x] = 1;
    vis[x] = 1;
	for(int i=0;i<son[x].size();i++){
		int v = son[x][i];
		if(!vis[v]){
		dep[v] = dep[x] +1;
		dfs1(v);
		size[x]+=size[v];	
		}
			
	}
}
void dfs2(int x){
    vis[x] = 1;
	for(int i=0;i<son[x].size();i++){
		int v = son[x][i];
		if(!vis[v]){
		dp[v] = dp[x] +size[1] - 2*size[v];
		dfs2(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);
	}
	dfs1(1);
	
	for(int i=1;i<=n;i++) dp[1]+=dep[i];
	
	memset(vis,0,sizeof(vis));
	dfs2(1);
	long long ans = -0x3f;
	int jd =999;
	for(int i=1;i<=n;i++){
		if(ans < dp[i]) ans = dp[i], jd = i;
	}
	cout<<jd;
}