[算法學習] 換根dp


換根dp

一般來說,我們做題的樹都是默認 \(1\) 為根的。但是有些題目需要計算以每個節點為根時的內容。
朴素的暴力:以每個點 \(u\) 作為 \(root\) 暴力dfs下去,復雜度\(O(n^2)\)
正確的做法:換根dp,復雜度\(O(n)\)

執行步驟

  1. 第一次掃描,先默認 \(root=1\) ,跑一遍 \(dfs\)
  2. 第二次掃描,從 \(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;
} 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM