[BZOJ1977][BeiJing2010组队]次小生成树 Tree(Kruskal+倍增LCA)

本文介绍了一种求解次小生成树问题的有效算法。通过分析最小生成树与次小生成树之间的关系,提出了一种基于树上倍增的解决方案,用于快速确定次小生成树的边权总和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

乱搞?????

Source

https://www.lydsy.com/JudgeOnline/problem.php?id=1977

Solution

一个结论:最小生成树和次小生成树的差别只有一条边。换句话说,次小生成树是最小生成树删掉一条边再加上图中的另一条边后得到的。
证明:反证法。假设最小生成树中有两条边 e1,e2 e 1 , e 2 ,权值和为 S1 S 1 ,将这两条边删掉后再加边 e3,e4 e 3 , e 4 后得到次小生成树,权值和 S2 S 2
那么一定有 S1e1e2+e3+e4=S2 S 1 − e 1 − e 2 + e 3 + e 4 = S 2 e3+e4>e1+e2 e 3 + e 4 > e 1 + e 2
得出 e3>e1 e 3 > e 1 e4>e2 e 4 > e 2
因此 S1e1+e3 S 1 − e 1 + e 3 S1e2+e4 S 1 − e 2 + e 4 两者中一定存在一者大于 S1 S 1 且小于 S2 S 2 ,因此次小生成树的权值和不是 S2 S 2 ,与假设矛盾。得证。
有了结论,就可以得到一个算法:
枚举非树边 e e ,表示将要加上的边。加上边 e 后,生成树上会出现一个环。因此要去掉的边一定是生成树上 u u v 的路径上的边之一( u u v 为边 e e 的两端点)。
由于要求严格次小,因此如果 u v v 的路径上的最大边权等于边 e 的边权,那么要删去的边就是 u u v 的路径上的次大边,否则要删去的是 u u v 的路径上的最大边。每次枚举完加入的边 e e 并找出删掉的边后,就可以更新答案,得到的权值和最小的结果就是次小生成树的边权和。
于是问题转化为:多组询问,每次询问一条路径上的边权的最大值和次大值。
显然用树上倍增解决。
f[u][i] u u 向上跳 2i 步到达的节点。
转移:

f[u][i+1]=f[f[u][i]][i] f [ u ] [ i + 1 ] = f [ f [ u ] [ i ] ] [ i ]

g0[u][i] g 0 [ u ] [ i ] u u 向上跳 2i 步经过边的最大边权。
转移:
g0[u][i+1]=max(g0[u][i],g0[f[u][i]][i]) g 0 [ u ] [ i + 1 ] = max ( g 0 [ u ] [ i ] , g 0 [ f [ u ] [ i ] ] [ i ] )

g1[u][i] g 1 [ u ] [ i ] u u 向上跳 2i 步经过边的次大边权。如果不存在则为 − ∞
转移分三种情况:
g0[u][i]<g0[f[u][i]][i] g 0 [ u ] [ i ] < g 0 [ f [ u ] [ i ] ] [ i ] 时:
g1[u][i+1]=max(g0[u][i],g1[f[u][i]][i]) g 1 [ u ] [ i + 1 ] = max ( g 0 [ u ] [ i ] , g 1 [ f [ u ] [ i ] ] [ i ] )

g0[u][i]>g0[f[u][i]][i] g 0 [ u ] [ i ] > g 0 [ f [ u ] [ i ] ] [ i ] 时:
g1[u][i+1]=max(g1[u][i],g0[f[u][i]][i]) g 1 [ u ] [ i + 1 ] = max ( g 1 [ u ] [ i ] , g 0 [ f [ u ] [ i ] ] [ i ] )

g0[u][i]=g0[f[u][i]][i] g 0 [ u ] [ i ] = g 0 [ f [ u ] [ i ] ] [ i ] 时:
g1[u][i+1]=max(g1[u][i],g1[f[u][i]][i]) g 1 [ u ] [ i + 1 ] = max ( g 1 [ u ] [ i ] , g 1 [ f [ u ] [ i ] ] [ i ] )

在倍增求 LCA 的过程中,按照同样的方法合并路径上的 g0 g 0 g1 g 1 ,就能求出路径上边权的最大值和次大值。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Edge(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 1e5 + 5, L = 2e5 + 5, M = 3e5 + 5, Z = 20, INF = 0x3f3f3f3f;
int n, m, ecnt, fa[N], nxt[L], adj[N], go[L], val[L], f[N][Z], g0[N][Z],
g1[N][Z], sg0, sg1, dep[N]; ll mst, ans = 1ll << 62; bool sel[M];
struct cyx {int x, y, z;} a[M];
inline bool comp(const cyx &a, const cyx &b) {return a.z < b.z;}
int cx(int x) {if (fa[x] != x) fa[x] = cx(fa[x]); return fa[x];}
bool zm(int x, int y) {
    int ix = cx(x), iy = cx(y); if (ix != iy) return fa[iy] = ix, 1; return 0;
}
void add_edge(int u, int v, int w) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = w;
}
void dfs(int u, int fu) {
    int i; f[u][0] = fu; dep[u] = dep[fu] + 1; For (i, 0, 16) {
        f[u][i + 1] = f[f[u][i]][i]; g0[u][i + 1] = max(g0[u][i], g0[f[u][i]][i]);
        int x = g0[u][i], y = g0[f[u][i]][i];
        if (x < y) g1[u][i + 1] = max(x, g1[f[u][i]][i]);
        else if (x > y) g1[u][i + 1] = max(g1[u][i], y);
        else g1[u][i + 1] = max(g1[u][i], g1[f[u][i]][i]);
    }
    Edge(u) g0[v][0] = val[e], g1[v][0] = -INF, dfs(v, u);
}
void orz(int u, int v) {
    sg0 = sg1 = -INF; int i, x, y; if (dep[u] < dep[v]) swap(u, v);
    Rof (i, 17, 0) {
        if (dep[f[u][i]] >= dep[v]) {
            x = sg0; y = g0[u][i]; if (x < y) sg1 = max(x, g1[u][i]);
            else if (x > y) sg1 = max(sg1, y);
            else sg1 = max(sg1, g1[u][i]); sg0 = max(sg0, g0[u][i]); u = f[u][i];
        }
        if (u == v) return;
    }
    Rof (i, 17, 0) if (f[u][i] != f[v][i]) {
        x = sg0; y = g0[u][i]; if (x < y) sg1 = max(x, g1[u][i]);
        else if (x > y) sg1 = max(sg1, y); else sg1 = max(sg1, g1[u][i]);
        sg0 = max(sg0, g0[u][i]); u = f[u][i];
        x = sg0; y = g0[v][i]; if (x < y) sg1 = max(x, g1[v][i]);
        else if (x > y) sg1 = max(sg1, y); else sg1 = max(sg1, g1[v][i]);
        sg0 = max(sg0, g0[v][i]); v = f[v][i];
    }
    x = sg0; y = g0[u][0]; if (x < y) sg1 = max(x, g1[u][0]);
    else if (x > y) sg1 = max(sg1, y); else sg1 = max(sg1, g1[u][0]);
    sg0 = max(sg0, g0[u][0]); u = f[u][0];
    x = sg0; y = g0[v][0]; if (x < y) sg1 = max(x, g1[v][0]);
    else if (x > y) sg1 = max(sg1, y); else sg1 = max(sg1, g1[v][0]);
    sg0 = max(sg0, g0[v][0]); v = f[v][0];
}
int main() {
    int i; n = read(); m = read(); For (i, 1, m)
        a[i].x = read(), a[i].y = read(), a[i].z = read();
    For (i, 1, n) fa[i] = i; sort(a + 1, a + m + 1, comp); For (i, 1, m)
        if (zm(a[i].x, a[i].y)) mst += a[i].z,
        add_edge(a[i].x, a[i].y, a[i].z), sel[i] = 1;
    dfs(1, 0); For (i, 1, m) if (!sel[i])
        orz(a[i].x, a[i].y), ans = min(ans, mst + a[i].z -
            (a[i].z == sg0 ? sg1 : sg0)); cout << ans << endl; return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值