換根dp
一般來說,我們做題的樹都是默認 \(1\) 為根的。但是有些題目需要計算以每個節點為根時的內容。
朴素的暴力:以每個點 \(u\) 作為 \(root\) 暴力dfs下去,復雜度\(O(n^2)\);
正確的做法:換根dp,復雜度\(O(n)\)。
執行步驟
- 第一次掃描,先默認 \(root=1\) ,跑一遍 \(dfs\);
- 第二次掃描,從 \(root=1\) 開始,每次從 \(u\) 到 \(v\) 節點時,計算根從 \(u\) 轉移到 \(v\) 時的貢獻變化。
很顯然,換根dp是在兩個\(dfs\)中完成的,下面我們介紹一下如何運用它。
例題1 Accumulation Degree
題目鏈接:South Central China 2008 Accumulation Degree
Description
給你一顆有 \(n\) 個節點的樹,每一條邊連接 \(u_i\) 和 \(v_i\),流量為 \(fl_i\) ,你需要找出一個點作為 \(root\),並最大化從該點出發到所有葉子節點的流量最大值。
多組數據。(PS:題意讀不懂的可以結合題目中的圖理解,類似網絡流的流法)
數據范圍 \(1 \le n\le 200000\),並且 \(\sum n \le 200000\)
時間限制 \(1000\ ms\)
Solution
我們先默認這棵樹以 \(1\) 為根,跑一次 \(dfs\)。
定義 \(flow[i]\) 表示以 \(i\) 為根的子樹中流量最大值。
那么,\(u\) 節點從兒子 \(v\) 得到的流量為:
1.若\(v\)為葉子節點,那么\(flow[u] += flow[v]\)(可以直接流過來);
2.若\(v\)為非葉子節點,那么\(flow[u] += min(flow[v], fl(u, v))\)(\(u\)和\(v\)相連的邊有流量限制)。
這樣,我們得到了以 \(1\) 為根時的答案,記為 \(f[1]\),它的值等於 \(flow[1]\)。
考慮如何換根。
從 \(u\) 為根轉移到兒子 \(v\) 為根, \(f[v]\) 包括兩部分:一部分是從 \(v\) 流向自己的子樹,一部分是從 \(v\) 往父節點走。
那么貢獻的變化是第二部分造成的,原本的貢獻是 \(flow[u] - min(flow[v], fl(u, v))\),現在加上 \(u\) 到 \(v\) 這條邊的流量限制,所以新的貢獻是 \(min(fl(u, v), flow[u] - min(flow[v], fl(u, v)))\)。
注意如果 \(u\) 的度為 \(1\),則需要特殊處理。
再來一個 \(dfs\) 轉移即可。
復雜度 \(O(n)\),可以通過本題。
Code
例題2 STA-Station
題目鏈接:POI2008 STA-Station
Description
給你一顆有 \(n\) 個節點的樹,你需要找出一個點作為 \(root\) ,並最大化 \(\sum_{i=1}^{n} dep_i\)。
其中 \(dep_i\) 表示以 \(root\) 為根時,\(i\)節點的深度。
數據范圍 \(1\le n\le 10^6\)
時間限制 \(1000\ ms\)
Solution
我們先默認這顆樹以 \(1\) 為根,跑一次 \(dfs\),記錄\(dep[i]\) 和\(size[i]\)。
接下來,定義 \(f_i\) 表示以 \(i\) 為根時的 \(dep[i]\) 之和。
顯然,\(f[1] = \sum_{i=1}^{n} dep[i]\)。
當我們從 \(u\) 轉移到兒子 \(v\) 時,以 \(v\) 為根的子樹內的所有節點 \(dep\) 值都減一,以外的所有節點 \(dep\) 值都加一。
於是有: \(f[v] = f[u] - size[v] + (n - size[v]) = f[u] + n - 2 * size[v]\)。
答案即為 \(max_{i=1}^{n} f[i]\) 的 \(i\)。
復雜度 \(O(n)\),卡卡常可以通過本題。
Code
這個題目卡\(vector\),能把用\(STL\)的完美卡飛。所以我改成前向星了嗚嗚嗚。
// Author: wlzhouzhuan
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define Each(i) for (rint i = head[u]; i; i = edge[i].nxt)
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
const int N = 1000005;
struct Edge {
int to, nxt;
} edge[N << 1];
int head[N], tot;
void add(int u, int v) {
edge[++tot] = {v, head[u]};
head[u] = tot;
}
int n;
ll f[N];
int sz[N], dep[N];
void dfs1(int u, int fa) {
sz[u] = 1;
dep[u] = dep[fa] + 1;
Each(i) {
int v = edge[i].to;
if (v == fa) continue;
dfs1(v, u);
sz[u] += sz[v];
}
}
void dfs2(int u, int fa) {
Each(i) {
int v = edge[i].to;
if (v == fa) continue;
f[v] = f[u] + n - 2ll * sz[v];
dfs2(v, u);
}
}
int main() {
ios :: sync_with_stdio(false); cin.tie(0);
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dfs1(1, 0);
for (int i = 1; i <= n; i++) f[1] += dep[i];
dfs2(1, 0);
cout << max_element(f + 1, f + n + 1) - f << '\n';
return 0;
}