换根DP

换根\(DP\)属于树形\(DP\),是二次扫描,因为这类问题通常不会指定根结点,并且根节点的变化会对一些值,例如子结点深度和、点权和等产生影响。
通常需要二次\(DFS\),第一次\(DFS\)来处理深度,以及点权和之类的问题,第二次\(DFS\)开始动态规划。

\(u\)为当前结点,\(v\)为当前结点的叶子结点。首先需要用\(s_i\)来表示以\(i\)为根的子树中的结点个数,并且有\(s_u = 1 + \sum s_v\),那么这个通过一遍\(DFS\)就可以办得到。

考虑状态转移,令\(f_u\)表示以\(u\)为根时,所有结点深度之和,那么进行换根,让\(v\)为根,那么转移就是\(f_v <- f_u\),那么这时候各结点的深度可以分为一下两种情况:

  • 所有在\(v\)的子树上的结点深度都减少\(1\),那么总深度之和就减少\(s_v\)
  • 所有不在\(v\)的子树上的结点深度都增加\(1\),那么总深度和就增加\(n - s_v\)
    那么状态转移方程就可以推出来了,\(f_v = f_u - s_v + n - s_v = f_u + n - 2 * s_v\)
    于是第二遍\(DFS\)处理这个东西就好,这样就能求出以所有结点为根节点的深度和了。

例题:[POI2008]STA-Station
代码:

// Problem: P3478 [POI2008]STA-Station
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3478
// Memory Limit: 125 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

using namespace std;

const int N = 1E6 + 10, M = N * 2;

int Size[N];
int h[N], e[M], ne[M], idx;
int depth[N];
long long f[N];
int n;

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int dfs1(int u, int fa) {
	Size[u] = 1;
	if (fa == -1) depth[u] = 0;
	else depth[u] = depth[fa] + 1;
	
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (j == fa) continue;
		int S = dfs1(j, u);
		Size[u] += S;
	}
	
	return Size[u];
}

void dfs2(int u, int fa) {
	for (int i = h[u]; i != -1; i = ne[i]) {
		int v = e[i];
		if (v == fa) continue;
		f[v] = f[u] - 2 * Size[v] + n;
		dfs2(v, u);	
	}
}

int main() {
	scanf("%d", &n);
	memset(h, -1, sizeof h);
	for (int i = 1; i <= n - 1; i++) {
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b), add(b, a);	
	}
	
	dfs1(1, -1);
	
	for (int i = 1; i <= n; i++) f[1] += depth[i];
	
	//换根
	//从u->v,在v子树上的深度减一,一共减去Size[v]
	//不在v子树上的,深度都增加1,总共增加n - Size[v]
	//那么从u到v答案变化,f[v] = f[u] - Size[v] + n - Size[v] = f[u] - 2 * Size[v] + n
	dfs2(1, -1);
	
	int res = 1;
	long long cnt = 0;
	for (int i = 1; i <= n; i++) {
		if (f[i] > cnt) {
			cnt = f[i];
			res = i;
		}
	}
	
	printf("%d\n", res);
	
    return 0;
}
posted @ 2021-09-30 18:10  Xxaj5  阅读(408)  评论(0编辑  收藏  举报