[题]次小生成树 —— 标签 # LCA

这是题目:次小生成树

参考博客Orz:
严格次小生成树[BJWC2010]
本文作者:秦淮岸灯火阑珊

大佬讲的很清晰,以下是我的个人理解:
首先要做的,是得到最小生成树
然后呢,我们将目光看向那些被忽略的边(也叫做对最小生成树而言多余的边)
可以简单地证明:将任何一个多余的边加进来替换掉某一个边,所生成的树权值之和一定比最小生成树大
于是关键来了:到底用哪一条边替换哪一条边所生成的才是次小生成树?
有一点补充的是:任何一条多余的边加进来后,都会在最小生成树中形成一个回环,而它所要替换的,就是那个环中最大的那个边。
补充证明:为什么要将最大的边替换掉?
为了保证它能够成为候选的次小生成树:当多余的边插进来后,整体上这棵树就已经不是最小生成树了,而我们要找的,就是非最小生成树中最小的那个。所以我们要保证新的生成树权值最小。
于是要将新插进来的边将这个环上的最大边替换掉。
OK,那么现在我们已经明确了操作:将多余的边一条条插进来替换掉它加入后形成的环中的最大边,然后比较新的生成树的权值,那么怎么才能快速找到环中的最大边?
利用LCA
可以确定,加入新的边(x,y,z)后,环的另一部分一定是最小生成树上x到y的路径,所以最大边一定在这条路径(x,y)上面。
利用LCA中ST表区间最值的性质,可以记录当前区间内的最大边。
转移的话就是两个小的区间进行比较,选出两者中各自的最大边以及严格次大边中最大的两个。
注意:确定大区间内的最大边以及次大边时,要同时考虑两个小区间中的最大边和次大边。
引用一下参考的博客(大佬勿怪
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
那么现在就很明确要怎么做了,开始码字……
好晕啊……涉及的知识糅合起来真的乱……
用到的知识有:
1. 最小生成树:kruscal
1. LCA
三、乱七八糟的不知名的操作……

完结感言:
//我是万万没想到它那个答案能够大到这个程度……
INF的值卡了我一个上午,其他地方检查了五次,重打了一次QAQ
终于能够解释最后两个点为什么跑出来的总是比答案要小那么多了QVQ
原因是longlong 字节数要比int长一倍……
平时习惯了0x3f3f3f3f是最大的,但是longlong中0x3f3f3f3f3f3f3f3f才是最大哒!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10, M = 6e5 + 10;
//我是万万没想到它那个答案能够大到这个程度……INF卡了我一个上午,其他地方检查了五次,重打了一次QAQ
#define INF 0x3f3f3f3f3f3f3f3f
int h[N], ne[M], e[M], idx, p[N], fa[N][20], n, m, dep[N];
ll w[M], ans, sum, val1, val2, dp[2][N][20];
struct E {
    int a, b, vis;
    ll w;
} s[M];
bool cmp(E a, E b) { return a.w < b.w; }
void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; }
int find(int x) { return x == p[x] ? x : p[x] = find(p[x]); }
void up2(int val) {
    if (val > val1)
        val2 = val1, val1 = val;
    else if (val > val2 && val != val1)
        val2 = val;
}
void up1(int x, int k) {
    up2(dp[0][x][k]);
    up2(dp[1][x][k]);
}
void kru() {
    sort(s + 1, s + 1 + m, cmp);
    for (int i = 1; i <= m; i++) {
        int x = find(s[i].a), y = find(s[i].b);
        if (x == y)
            continue;
        p[x] = y;
        s[i].vis = 1;
        sum += s[i].w;
        add(s[i].a, s[i].b, s[i].w);
        add(s[i].b, s[i].a, s[i].w);
    }
}
void bfs(int root) {
    dep[root] = 0;
    queue<int> q;
    q.push(root);
    while (q.size()) {
        int t = q.front();
        int len = (int)log2(dep[t]);
        q.pop();
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (j == fa[t][0])
                continue;
            dep[j] = dep[t] + 1;
            fa[j][0] = t;
            dp[0][j][0] = w[i], dp[1][j][0] = -INF;
            q.push(j);
            for (int k = 1; k <= len; k++) {
                fa[j][k] = fa[fa[j][k - 1]][k - 1];
                //最大值不同的两个情况
                if (dp[0][j][k - 1] > dp[0][fa[j][k - 1]][k - 1]) {
                    dp[0][j][k] = dp[0][j][k - 1];
                    dp[1][j][k] = max(dp[1][j][k - 1], dp[0][fa[j][k - 1]][k - 1]);
                } else if (dp[0][j][k - 1] < dp[0][fa[j][k - 1]][k - 1]) {
                    dp[0][j][k] = dp[0][fa[j][k - 1]][k - 1];
                    dp[1][j][k] = max(dp[0][j][k - 1], dp[1][fa[j][k - 1]][k - 1]);
                }
                //两者最大值相同,次大就是两个次大中大的那个
                else {
                    dp[0][j][k] = dp[0][j][k - 1];
                    dp[1][j][k] = max(dp[1][j][k - 1], dp[1][fa[j][k - 1]][k - 1]);
                }
            }
        }
    }
}
void lca(int x, int y) {
    val2 = val1 = -INF;
    if (dep[x] < dep[y])
        swap(x, y);
    while (dep[x] > dep[y]) {
        int t = (int)log2(dep[x] - dep[y]);
        up1(x, t), x = fa[x][t];
    }
    if (x == y)
        return;
    for (int t = (int)log2(dep[x]); t >= 0; t--) {
        if (fa[x][t] != fa[y][t]) {
            up1(x, t), up1(y, t);
            x = fa[x][t], y = fa[y][t];
        }
    }
    up1(x, 0), up1(y, 0);
}
void init() {
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) p[i] = i;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) scanf("%d%d%lld", &s[i].a, &s[i].b, &s[i].w);
    init();  //这是初始化
    kru();   //这是建立最小生成树
    bfs(1);  //这是跑最近公共祖先的预处理
    ans = INF;
    for (int i = 1; i <= m; i++) {
        if (s[i].vis)
            continue;
        lca(s[i].a, s[i].b);  //每输入一次就跑一次LCA,返回的结果改为val1和val2
        if (val1 != s[i].w)
            ans = min(ans, sum + s[i].w - val1);
        else
            ans = min(ans, sum + s[i].w - val2);
    }
    printf("%lld", ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值