1. 程式人生 > 實用技巧 >Algorithm Note 7 Kruskal重構樹演算法/再談NOIP2013貨車運輸

Algorithm Note 7 Kruskal重構樹演算法/再談NOIP2013貨車運輸

//對Kruskal演算法的介紹以sdsc課件為基礎,原作者為djh老師。

Kruskal 重構樹演算法是一種基於 Kruskal 的變形。

在使用並查集合並兩個集合 (兩棵樹) 的時候,我們新建一個點作為樹根,點權為連線兩個集合的邊的邊權。

我們考慮這樣得到的一棵樹會有什麼性質。

1. 原圖中的所有節點都是葉子。因為合併過程中只有新結點能作為父節點。

2. 總共有 O(n) 個節點。

3. 是一棵二叉樹。

4. 如果把葉子節點的權值看作 0,那麼滿足大根堆的性質。顯然,由於按邊權排序,新加入的點權大於一定兩個子結點。

5. 最小生成樹上兩點間路徑邊權的最大值是他們重構樹上LCA的點權大小。因為當兩個點首次在重構樹上聯通時也在原生成樹上聯通,它們之間路徑的最大值一定時當時的根,也就是LCA。

程式碼實現

 1 for(int i = 1; i <= n; ++i) fa[i] = i;
 2 sort(e + 1, e + 1 + m);
 3 int node = n;
 4 for(int i = 1; i <= m; ++i) {
 5     int fu = getf(e[i].u), fv = getf(e[i].v);
 6     if(fu == fv) continue;
 7     val[++ node] = e[i].c;
 8     fa[node] = fa[u] = fa[v] = node;
 9     add(node, u); add(node, v);
10 cnt ++; 11 if(cnt == n - 1) break; 12 }

然後課件上放了這個模板題和NOI2018 歸程。

不過模版題沒處交,歸程咋都調不出來,於是我想起以前寫的一個題,NOIP2013(洛谷P1967)貨車運輸。

https://www.luogu.com.cn/problem/P1967

題意:給定一張地圖,n個地點m條道路,每段道路有最大限重。q次詢問從u到v兩地之間貨車載重量的最大值,如果兩地不能互達輸出-1。

所以這不就是模板題嗎。。。

不過我還是調了很久。在sdsc剛養成all in stl的習慣,經常犯一些i <= g[u].size()之類的錯誤。

然後注意這題是個森林,不能直接dfs(1, 0),而應該

for (int i = 1; i <= cnt; i++) {
    if (!vis[i]) {
      int f = find(i);
      dfs(f, 0);
    }
  }

為什麼是對的呢?因為建重構樹的過程保證了find(i)為重構樹的根。

以下是完整程式碼。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 6;
struct edge {
  int u, v, w;
  bool operator < (const edge &x) {
    return w > x.w;
  }
} e[N];
vector<int> g[N];
int n, m, q, cnt, fa[N], vis[N], val[N], d[N], f[N][30];
int find(int x) {
  return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void kruskal() {
  sort(e + 1, e + m + 1);
  for (int i = 1; i <= n; i++) fa[i] = i; 
  for (int i = 1; i <= m; i++) {
    int fu = find(e[i].u), fv = find(e[i].v);
    if (fu != fv) {
      val[++cnt] = e[i].w;
      fa[cnt] = fa[fu] = fa[fv] = cnt;
      g[cnt].push_back(fu); g[cnt].push_back(fv); g[fu].push_back(cnt); g[fv].push_back(cnt);
    }
  }
}
void dfs(int u, int fa) {
  d[u] = d[fa] + 1, f[u][0] = fa, vis[u] = 1;
  for (int i = 1; (1 << i) <= d[u]; i++) 
    f[u][i] = f[f[u][i-1]][i-1];
  for (int i = 0; i < g[u].size(); i++) {
    int v = g[u][i];
    if (v != fa) dfs(v, u);
  }
}
int lca(int x, int y) {
  if (d[x] < d[y]) swap(x, y);
  int k = d[x] - d[y];
  for (int i = 25; i >= 0; i--)
    if ((1 << i) & k) x = f[x][i];
  if (x == y) return x;
  for (int i = 25; i >= 0; i--)
    if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
  return f[x][0];  
}
int main() {
  cin >> n >> m; cnt = n;
  for (int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v >> e[i].w;
  kruskal();
  for (int i = 1; i <= cnt; i++) {
    if (!vis[i]) {
      int f = find(i);
      dfs(f, 0);
    }
  }
  cin >> q;
  while (q--) {
    int u, v;
    cin >> u >> v;
    if (find(u) != find(v)) cout << -1 << endl;
    else cout << val[lca(u, v)] << endl;
  }
  return 0;
}