次小生成树

次小生成树

题意

给定一张 n n n 个点 m m m 条边的无向图,求无向图的严格次小生成树。

设最小生成树的边权之和为 s u m sum sum,严格次小生成树就是指边权之和大于 s u m sum sum生成树中最小的一个。

输入格式

第一行包含两个整数 n n n m m m

接下来 m m m 行,每行包含三个整数 x x x y y y z z z,表示点 x x x 和点 y y y 之前存在一条边,边的权值为 z z z

输出格式

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

数据范围

N ≤ 1 0 5 , M ≤ 3 × 1 0 5 N\le10^5,M\le3 \times10^5 N105,M3×105

题解

​ 首先次小生成树与最小生成树一定只有一条边不一样,因为如果有两条不一样的话,那么就可以找这两条其中一条,在次小生成树中连上最小生成树去掉的那条边,就得到了一个环,因为这条边一定是比去掉那条大,所以把这条去掉,我们就得到了一个,比最小生成树大但是比当前次小生成树小的树,因此次小生成树与最小生成树一定只有一条边不一样

算法一 暴力枚举拆边

​ 我们可以用kruskal 把最小生成树求出来,然后从这个树上把任意一个 u -> v 的最大边和严格次大边求出来,之后遍历所有的u -> v,填上这个边生成一个环,如果原树中最大边不等于这个边,就去掉最大边,否则去掉次大边。

​ 算法的瓶颈在于求出任意两点之间的最大值和次大值需要 O ( n 2 ) O(n^2) O(n2)

Code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <queue>
#include <vector>
#include <map>
#include <unordered_map>
#include <cmath> 
#include <stack>
#include <iomanip>
#include <deque> 
#include <sstream>
#define x first
#define y second
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 510, M = 1e4 + 10, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
struct Node {
    int u, v, w;
    bool flag;
    bool operator<(const Node &t)const {
        return w < t.w;
    }
}edges[M];
int dist1[N][N], dist2[N][N];
int f[N];
int find(int x) {
    return x == f[x] ? x : f[x] = find(f[x]);
}
void dfs(int u, int fa, int mx1, int mx2, int d1[], int d2[]) { // 递推的求出,以每个点为根的唯一路径,树的特殊性质
        d1[u] = mx1, d2[u] = mx2;
        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            if (j == fa) continue;
            int t1 = mx1, t2 = mx2;
            // 最大值和次大值且不能相等
            if (w[i] > t1)  t2 = t1, t1 = w[i];
            else if (w[i] < t1 && w[i] > t2) t2 = w[i];
            dfs(j, u, t1, t2, d1, d2);    
         }
}   
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) f[i] = i;
    for (int i = 0; i < m; i ++ ) 
        cin >> edges[i].u >> edges[i].v >> edges[i].w;
    sort(edges, edges + m);
    LL sum = 0;
    // 把树生成出来
    for (int i = 0; i < m; i ++ ) {
        int a = edges[i].u, b = edges[i].v, w = edges[i].w;
        int fa = find(a), fb = find(b);
        if (fa != fb) {
            f[fa] = fb;
            sum += w;
            edges[i].flag = true; // 在树中, 为不在树中的做准备
            add(a, b, w), add(b, a, w);
        }
    }
        for (int i = 1; i <= n; i ++ )
        dfs(i, -1, 0, 0, dist1[i], dist2[i]); //任意一个点的距离到其他点的最大值和次大值
        LL res = 3e18;
        for (int i = 0; i < m; i ++ )
        if (!edges[i].flag) {
            int a = edges[i].u, b = edges[i].v, w = edges[i].w;
            if (w != dist1[a][b])  // 最小值和次小值
                res = min(res, sum + w - dist1[a][b]); // 拆边再加边
            else 
                res = min(res, sum + w - dist2[a][b]);
            
        }
        cout << res << endl;
    return 0;
}
// 次小生成树,一定是最小生成树去掉一个边,变成两个树以后再加入一条边所形成的
// 如何保证是次小的,每加上一条边都会形成一个环,然后把环上除刚加入的边的最大的一个
// 删掉即可,暴力枚举,res一定属于其中一种情况

算法二 倍增优化

​ 思想还是原来的思想,找一个边替换,考虑对找到任意两点之间的最大值和次大值进行优化。用 d 1 [ u ] [ j ] d1[u][j] d1[u][j]表示从 u u u这个点向上跳 2 j 2^j 2j步这条路径上的最大值, d 2 [ u ] [ j ] d2[u][j] d2[u][j]表示从 u u u这个点向上跳 2 j 2^j 2j步这条路径上的次大值,然后对于u -> v这条边进行进行替换原树中的时候,只需要找到 u, v的 lca,假设lca为p,那么这条路径的最大值和次大值都可以在找lca的往上跳的过程中顺带维护出来。这样时间复杂度就优化成了 O ( n l o g n ) O(nlogn) O(nlogn)

Code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <queue>
#include <vector>
#include <map>
#include <unordered_map>
#include <cmath> 
#include <stack>
#include <iomanip>
#include <deque> 
#include <sstream>
#define x first
#define y second
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 3 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
struct Node {
    int u, v, w;
    bool ok;
    bool operator< (const Node &t) const {
        return w < t.w;
    }
}edge[M];
int f[N], dep[N], fa[N][17], d1[N][17], d2[N][17];
int q[N];
int find(int x) {
    return x == f[x] ? x : f[x] = find(f[x]);
}
LL kruskal() {
    for (int i = 1; i <= n; i ++ ) f[i] = i;
    sort(edge, edge + m);
    LL res = 0;
    for (int i = 0; i < m; i ++ ) {
        int a = find(edge[i].u), b = find(edge[i].v), w = edge[i].w;
        if (a != b) {
            f[a] = b;
            res += w;
            edge[i].ok = true;
        }
    }
    return res;
}
void build() {
    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i ++ ) 
        if (edge[i].ok) {
            int a = edge[i].u, b = edge[i].v, w = edge[i].w;
            add(a, b, w), add(b, a, w);
        }
}
void bfs() {
    memset(dep, 0x3f, sizeof dep);
    dep[0] = 0, dep[1] = 1;
    q[0] = 1;
    int hh = 0, tt = 0;
    while (hh <= tt) {
        int t = q[hh ++];
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (dep[j] > dep[t] + 1) {
                dep[j] = dep[t] + 1;
                q[++ tt] = j;
                fa[j][0] = t;
                d1[j][0] = w[i], d2[j][0] = -INF; // 最大值和严格次大值
                for (int k = 1; k <= 16; k ++ ) {
                    int anc = fa[j][k - 1];  // 跳一半的祖先
                    fa[j][k] = fa[anc][k - 1];
                    int dd[4] = {d1[j][k - 1], d2[j][k - 1], d1[anc][k - 1], d2[anc][k - 1]}; // 四段 
                    d1[j][k] = d2[j][k] = -INF;
                    for (int u = 0; u < 4; u ++ ) {
                        int d = dd[u];
                        if (d > d1[j][k]) d2[j][k] = d1[j][k], d1[j][k] = d;
                        else if (d != d1[j][k] && d > d2[j][k]) d2[j][k] = d;
                    }
                }
            }
        }
    }
}
int lca(int a, int b, int w) {
    static int distance[N << 1];
    int cnt = 0;
    if (dep[a] < dep[b]) swap(a, b);
    for (int k = 16; k >= 0; k -- ) 
        if (dep[fa[a][k]] >= dep[b]) {
            distance[cnt ++] = d1[a][k];
            distance[cnt ++] = d2[a][k];
             a = fa[a][k];
        }   
          
    if (a != b) {
        for (int k = 16; k >= 0; k -- ) 
            if (fa[a][k] != fa[b][k]) {
                distance[cnt ++] = d1[a][k];
                distance[cnt ++] = d2[a][k];
                distance[cnt ++] = d1[b][k];
                distance[cnt ++] = d2[b][k];
                a = fa[a][k], b = fa[b][k];
            }
           distance[cnt ++] = d1[a][0],  distance[cnt ++] = d1[b][0]; // 一条路上的最大值只有他自己
    }
    int dist1 = -INF, dist2 = -INF;
    for (int i = 0; i < cnt; i ++ ) {
        int d = distance[i];
        if (d > dist1) dist2 = dist1, dist1 = d;
        else if (d != dist1 && d > dist2) dist2 = d;
    }
    if (w != dist1) return w - dist1;
    return w - dist2;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 0; i < m; i ++ ) {
        int a, b, c;
        cin >> a >> b >> c;
        edge[i] = {a, b, c};
    }
    LL sum = kruskal();
    build();
    bfs();
    LL res = 1e18;
    for (int i = 0; i < m; i ++ ) 
        if (!edge[i].ok) {
            int a = edge[i].u, b = edge[i].v, w = edge[i].w;
            res = min(res, sum + lca(a, b, w));
        }
    cout << res << endl;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值