题解 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) 后可以用 ( ext{LCA}) 来求出最近公共祖先。在求 ( ext{LCA}) 的过程中可以用 (mx) 数组来求出路径上的最大值,用 (cmx) 数组求出路径上的次大值。

( ext{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;
}
原文地址:https://www.cnblogs.com/Ryan-juruo/p/15222816.html