演算法學習 換根dp

2022-05-05 12:03:10 字數 3133 閱讀 8305

一般來說,我們做題的樹都是預設 \(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為根的結果能否快速求出以它某兒...