1. 程式人生 > 其它 >題解 P4180 [BJWC2010]嚴格次小生成樹

題解 P4180 [BJWC2010]嚴格次小生成樹

題面

首先可以想到的是次小生成樹肯定和最小生成樹幾乎長一樣,次小生成樹僅僅只有某一條邊的決策不是最優的,兩者只有一條邊有差別。(容易想到,但我不會證明)

接著方法就自然而然的出來了,即求出最小生成樹後,列舉刪除最小生成樹的邊,然後再求新圖的最小生成樹。但這樣時間複雜度有點高,需要進行優化。

所以我們就想到這樣的優化方法:

  • 求出最小生成樹後,依次往樹上插入非樹上的一條邊,那麼樹上就有了一個環。
  • 把環上最大的除新插入的邊找到,並刪除它,得到了一個新的樹。
  • 對所有非樹邊都嘗試進行上述嘗試,得到的答案最小值就是次小生成樹了。

現在的問題是插入一條邊 \((u,v)\) 後,如何快速找到 \((u,v)\)

路徑之間的最大值。
快速查詢的辦法可以用倍增的思想來實現。注意到我們要求嚴格最小生成樹。所以如果新插入的邊和環上的最大邊權值一樣的話找到的新樹仍然是最小生成樹,而並非嚴格的次小生成樹。所以我們需要同時記錄環上的最大值和次大值。

下面講解求最大值的方法,次大值也類似,具體可以看程式碼。

在最小生成樹中,我們用 \(mx[i][j]\) 表示從樹節點 \(i\) 往根節點走 \(2^{j}\) 步路徑中的最大值,用 \(fa[i][j]\) 表示 \(i\) 節點往根上走 \(2^{j}\) 步對應的祖先。

那麼就有如下轉移方式:
\(fa[i][j]=fa[fa[i][j-1]][j-1]\)


\(mx[i][j]=\max(mx[i][j-1],mx[fa[i][j-1]][j-1])\)

給定節點 \(u,v\) 後可以用 \(\text{LCA}\) 來求出最近公共祖先。在求 \(\text{LCA}\) 的過程中可以用 \(mx\) 陣列來求出路徑上的最大值,用 \(cmx\) 陣列求出路徑上的次大值。

\(\text{Code}\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
struct node {
    int u, v, next;
    ll w;
} a[3 * N], E[3 * N];
bool cmp(node x, node y) {
    return x.w < y.w;
}
int p[N], eid, fa[N], f[N][22], d[N];
ll mx[N][22], cmx[N][22], W;
vector<node> G[3 * N];
bool used[3 * N];
void add(int u, int v, ll w) {
    E[eid].u = u;
    E[eid].v = v;
    E[eid].next = p[u];
    E[eid].w = w;
    p[u] = eid++;
}
void init() {
    memset(mx, -1, sizeof(mx));
    memset(cmx, -1, sizeof(cmx));
    memset(p, -1, sizeof(p));
    eid = 1;
    for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) {
    if (fa[x] == x) return fa[x];
    return fa[x] = find(fa[x]);
}
void kruskal() {
    sort(a, a + m, cmp);
    for (int i = 0; i < m; i++) {
        int fu = find(a[i].u), fv = find(a[i].v);
        if (fu != fv) {
            fa[fv] = fu;
            W += a[i].w;
            add(a[i].u, a[i].v, a[i].w);
            add(a[i].v, a[i].u, a[i].w);
            used[i] = true;
        }
    }
}
void dfs(int u) {
    d[u] = d[f[u][0]] + 1;
    for (int i = p[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if (v != f[u][0]) {
            mx[v][0] = E[i].w;
            cmx[v][0] = -INF;
            f[v][0] = u;
            dfs(v);
        }
    }
}
void init_LCA() {
    for (int i = 1; i <= n; i++) fa[i] = 0;
    dfs(1);
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i <= n; i++) {
            f[i][j] = f[f[i][j - 1]][j - 1];
            mx[i][j] = max(mx[i][j - 1], mx[f[i][j - 1]][j - 1]);
            cmx[i][j] = max(cmx[i][j - 1], cmx[f[i][j - 1]][j - 1]);
            if (mx[i][j - 1] > mx[f[i][j - 1]][j - 1]) cmx[i][j] = max(cmx[i][j], mx[f[i][j - 1]][j - 1]);
            if (mx[i][j - 1] < mx[f[i][j - 1]][j - 1]) cmx[i][j] = max(cmx[i][j], mx[i][j - 1]);
        } 
    }
}
ll LCA(int x, int y, ll w) {
    if (d[x] < d[y]) swap(x, y);
    ll maxv = -INF;
    for (int i = 20; i >= 0; i--) 
        if (d[f[x][i]] >= d[y]) {
            if (mx[x][i] != w) maxv = max(maxv, mx[x][i]);
            else maxv = max(maxv, cmx[x][i]);
            x = f[x][i];
        }
    if (x == y) return maxv;
    for (int i = 20; i >= 0; i--) {
        if (f[x][i] != f[y][i]) {
            if (mx[x][i] != w) maxv = max(maxv, mx[x][i]);
            else maxv = max(maxv, cmx[x][i]);
            if (mx[y][i] != w) maxv = max(maxv, mx[y][i]);
            else maxv = max(maxv, cmx[y][i]);
            x = f[x][i];
            y = f[y][i];
        }
    }
    if (mx[x][0] != w) maxv = max(maxv, mx[x][0]);
    else maxv = max(maxv, cmx[x][0]);
    if (mx[y][0] != w) maxv = max(maxv, mx[y][0]);
    else maxv = max(maxv, cmx[y][0]);
    return maxv;
}
ll getlen(int u, int v, ll w) {
    ll maxv = LCA(u, v, w);
    return W + w - maxv;
}
int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) cin >> a[i].u >> a[i].v >> a[i].w;
    init();
    kruskal();
    init_LCA();
    ll ans = INF;
    for (int i = 0; i < m; i++) 
        if (!used[i]) 
            ans = min(ans, getlen(a[i].u, a[i].v, a[i].w));
    cout << ans << endl;
    return 0;
}