vijos1070_关于次小生成树的求法

本文介绍了次小生成树的高效算法实现,通过边交换思想和最小瓶颈路的概念,提出了O(n²+mlogm)复杂度的解决方案,并提供了完整的代码示例。
(某日, xzz出现了卖萌的一幕)
jzz:   DG, 你会求最小生成树吗?

DG:   会呀!
jzz:   那次小生成树呢?
DG:    这个嘛...... -_-||不会————枚举然后暴力吗?
jzz:   戚, O(nmlogm) 早炸飞了。
DG:    到底怎么做嘛?
jzz:   问问DL, 说不定他心情好会告诉你的。
DL:   那么问题来了, 如何用O(n²+mlogm)的方法求次小生成树呢?


    首先, 我们肯定知道最小生成树的求法, 什么kruskal啊, prim啊, 随随便便就水了很多题不是吗?
    在面对次小生成树问题的时候, 可以选择枚举原MST中的每一条边进行删除, 然后跑MST的算法(当然, 可能有更好的暴力方法, 这里就不详细描述了), 这样一来, 复杂度就到了O(nmlogm)的级别, 小一点的数据也是可以的。不过我们要追求更优的话, 就需要用到另一种方法。
一个概念:最小瓶颈路
    在无向图中求一条连接两点的路径, 使路径上权值最大的边尽可能的小, 则这条路径称为最小瓶颈路。
    求法也很简单, 在原图中构建MST, 则在MST上两点间的路径即为最小瓶颈路了。证明也非常简单, 只要维护最大边最小的话, 可以将先前的所有边进行排序, 然后依次添加, 直到两点连通为止。 再看先前的操作, 不就是kruskal吗?
具体过程:
    在熟悉了上面的概念以后, 接下来的过程就非常简单了。 这次我们枚举要添加哪一条边, 加上这条边的权值, 然后将这条边连接的两点的最小瓶颈路上的最大边权减去, 就得到了一棵新的生成树。 这就是“边交换”的过程, 毕竟次小生成树是由最小生成树删去一条边再添加一条新边得到的嘛, 这一点容易证明。 于是, 我们用O(m)的时间枚举新添加的边, 然后用O(1)的时间更新答案, 最后输出。 之前MST的复杂度为O(mlogm), 求最小瓶颈路上的最大权值为O(n²), 总复杂度即为O(n²+mlogm)。

题目:https://vijos.org/p/1070

#include <cstdio>
#include <algorithm>
#define N 500 + 10
#define M 150000
#define INF 1000000000

using namespace std;

struct line
{
    int l, r, w;
}c[M];
struct edge
{
    int fr, to, w, next;
}e[2*N];
int n, m, num, now, ans1, ans2 = INF;
int fa[N], p[N], d[N][N], used[M];
bool cmp(line a, line b)
{
    return a.w < b.w;
}
int find(int x)
{
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
int read()
{
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    int x = 0;
    while(c >= '0' && c <= '9')
    {
        x = 10*x + c - '0';
        c = getchar();
    }
    return x;
}
void add(int x, int y, int z)
{
    e[++num].fr = x;
    e[num].to = y;
    e[num].w = z;
    e[num].next = p[x];
    p[x] = num;
}
void init()
{
    n = read(), m = read();
    for (int i = 1; i <= m; ++i)
    {
        c[i].l = read();
        c[i].r = read();
        c[i].w = read();
    }
}
void kruskal()
{
    for (int i = 1; i <= n; ++i)
    fa[i] = i;
    sort(c+1, c+m+1, cmp);
    for (int i = 1; i <= m; ++i)
    {
        int fl = find(c[i].l), fr = find(c[i].r);
        if (fl != fr)
        {
            used[i] = 1;
            fa[fl] = fr;
            ans1 += c[i].w;
            add(c[i].l, c[i].r, c[i].w);
            add(c[i].r, c[i].l, c[i].w);
            if (n - 1 == num / 2) return;
        }
    }
}//克鲁斯卡尔
void dfs_mini(int x, int fr, int maxs)
{
    d[now][x] = maxs;
    for (int i = p[x]; i; i = e[i].next)
    {
        int k = e[i].to;
        if (k != fr) dfs_mini(k, x, max(maxs, e[i].w));
    }
}//求最小瓶颈路上最大权值
void deal()
{
    kruskal();
    if (n - 1 == num / 2) printf("Cost: %d\n", ans1);
    else
    {
        printf("Cost: -1\n");
        printf("Cost: -1\n");
        return;
    }
    for (now = 1; now <= n; ++now)
    dfs_mini(now, 0, 0);
    for (int i = 1; i <= m; ++i)
    if (!used[i] && ans1 - d[c[i].l][c[i].r] + c[i].w < ans2)
    ans2 = ans1 - d[c[i].l][c[i].r] + c[i].w;//枚举并更新
    if (ans2 != INF) printf("Cost: %d\n", ans2);
    else printf("Cost: -1\n");
}
int main()
{
    init();
    deal();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值