一般來說,我們做題的樹都是預設 \(1\) 為根的。但是有些題目需要計算以每個節點為根時的內容。
樸素的暴力:以每個點 \(u\) 作為 \(root\) 暴力dfs下去,複雜度\(o(n^2)\);
正確的做法:換根dp,複雜度\(o(n)\)。
第一次掃瞄,先預設 \(root=1\) ,跑一遍 \(dfs\);
第二次掃瞄,從 \(root=1\) 開始,每次從 \(u\) 到 \(v\) 節點時,計算根從 \(u\) 轉移到 \(v\) 時的貢獻變化。
很顯然,換根dp是在兩個\(dfs\)中完成的,下面我們介紹一下如何運用它。
給你一顆有 \(n\) 個節點的樹,每一條邊連線 \(u_i\) 和 \(v_i\),流量為 \(fl_i\) ,你需要找出乙個點作為 \(root\),並最大化從該點出發到所有葉子節點的流量最大值。
多組資料。(ps:題意讀不懂的可以結合題目中的圖理解,類似網路流的流法)
資料範圍 \(1 \le n\le 200000\),並且 \(\sum n \le 200000\)
時間限制 \(1000\ ms\)
我們先預設這棵樹以 \(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)\),可以通過本題。
給你一顆有 \(n\) 個節點的樹,你需要找出乙個點作為 \(root\) ,並最大化\(\sum_^ dep_i\)。
其中 \(dep_i\) 表示以 \(root\) 為根時,\(i\)節點的深度。
資料範圍 \(1\le n\le 10^6\)
時間限制 \(1000\ ms\)
我們先預設這顆樹以 \(1\) 為根,跑一次 \(dfs\),記錄\(dep[i]\) 和\(size[i]\)。
接下來,定義 \(f_i\) 表示以 \(i\) 為根時的 \(dep[i]\) 之和。
顯然,\(f[1] = \sum_^ dep[i]\)。
當我們從 \(u\) 轉移到兒子 \(v\) 時,以 \(v\) 為根的子樹內的所有節點 \(dep\) 值都減一,以外的所有節點 \(dep\) 值都加一。
於是有: \(f[v] = f[u] - size[v] + (n - size[v]) = f[u] + n - 2 * size[v]\)。
答案即為 \(max_^ f[i]\) 的 \(i\)。
複雜度 \(o(n)\),卡卡常可以通過本題。
這個題目卡\(vector\),能把用\(stl\)的完美卡飛。所以我改成前向星了嗚嗚嗚。
// author: wlzhouzhuan
#include 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 #define mp(a, b) make_pair(a, b)
#define each(i) for (rint i = head[u]; i; i = edge[i].nxt)
inline int read()
while (isdigit(op))
return neg * x;
}inline void print(int x)
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}const int n = 1000005;
struct edge edge[n << 1];
int head[n], tot;
void add(int u, int v) ;
head[u] = tot;
}int n;
ll f[n];
int sz[n], dep[n];
void dfs1(int u, int fa)
}void dfs2(int u, int fa)
}int main()
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;
}
樹形 dp 換根 dp
樹形dp 樹形動歸一般是依賴於dfs的,根據動歸的後效性,父節點的狀態一般都依賴子節點的狀態以某種方式轉移而來 換根的p2015 設f i j 表示i的子樹上保留j條邊最多蘋果數 p2279 狀態表示f x 0 覆蓋到x的爺爺和x整棵子樹 向上2層 最少個數 f x 1 覆蓋到x的父親和x子樹 向上...
樹形DP 換根DP
某些樹形dp問題中,我們要求的值是類似 以當前節點為根節點得到的答案 卻沒有給出固定的根節點,若仍然按照常規的樹形dp思路對每個點進行dp,我們對每乙個節點均進行一次 dfs 最後的複雜度是 o left n 2 right 如果我們先假設任意乙個點為根進行 dp,求出當前樹形結構下以每個點為根的子...
換根dp入門
本題是要你任選乙個點為根使得樹上的所有點深度之和最小。本題是要你任選乙個點為根使得樹上的所有點深度之和最小。首先考慮如果是指定根節點你會不會做 已知根節點的話,我們只需要一遍dfs 或者bfs 就可以求出每個點的深度,然後求和就可以了。然後我們考慮如果我們知道當前節點x為根的結果能否快速求出以它某兒...