《算法竞赛进阶指南》 次小生成树

该博客介绍了如何在无向图中寻找严格次小生成树。首先,通过Kruskal算法找到最小生成树并计算其边权之和。接着,预处理树边信息,包括跳跃路径上的最大值和次大值。然后,枚举非树边,结合最小生成树的边权和以及预处理信息,找到使边权和大于最小生成树且最小的边集,即为严格次小生成树。这种方法的时间复杂度为O(M)。
摘要由CSDN通过智能技术生成

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

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

输入格式

第一行包含两个整数 N 和 M。

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

输出格式

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

数据范围

N≤105,M≤3×105

输入样例:

5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6

输出样例:

11

若不知道最小生成树可以看看这篇:(18条消息) 次小生成树模板 :《信息学奥赛一本通》 , USACO 秘密的牛奶运输_wsh1931的博客-CSDN博客

时间复杂度O(m)

步骤:

1:先求出最小生成树,并记录树中的总权值sum。

2:预处理三个数组记录的是树边,fa[i][j], d1[i][j], d2[i][j]。fa[i][j]表示从点 i 跳2^j 步之后所跳到达的点为fa[i][j]。d1[i][j], d2[i][j]分别为从 i 点开始跳2 ^ j步之后所到达的点fa[i][j]中路径的最大值与次大值

1:先求出最小生成树。

3:枚举每条非树边a, b,权值为w。在最小生成树中找出 a 到 b 的最大的权值假设为d,找出sum + w - d他的值为大于sum的最小值即为严格最小生成树

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010, M = 300010, INF = 0x3f3f3f3f;

int n, m;
int p[N];
int depth[N];
int fa[N][17];
int d1[N][17], d2[N][17];
int h[N], e[M], ne[M], w[M], idx;

struct Edge
{
    int a, b, w;
    bool used;
    bool operator < (const Edge &W) const
    {
        return w < W.w;
    }
}edge[M];

void add(int a, int b, int c)//邻接表
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
    idx ++ ;
}

int find(int x)//并查集
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void bfs()
{
    queue<int> q;
    memset(depth, 0x3f, sizeof depth);
    
    depth[0] = 0, depth[1] = 1;//设置边界,若不了解看推荐的博客
    q.push(1);//这里以任意点为根节点都可以,我这里假设的是以1为根节点
    
    while (q.size())
    {
        int t = q.front();
        q.pop();
        
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            
            if (depth[j] > depth[t] + 1)//若j还未被更新
            {
                depth[j] = depth[t] + 1;
                fa[j][0] = t;//j往上跳2^0为点t
                q.push(j);
                d1[j][0] = w[i], d2[j][0] = -INF;//最大值为w[i],要求次大值没有初始化为-INF.
                
                for (int k = 1; k <= 16; k ++ )
                {
                    int anc = fa[j][k - 1];
                    fa[j][k] = fa[anc][k - 1];
                    int distance[4] = {d1[j][k - 1], d2[j][k - 1], d1[anc][k - 1], d2[anc][k - 1]};
                    //[j, j + 2 ^ k]步直接的最大值必在以上四个值中
                    d1[j][k] = -INF, d2[j][k] = -INF;//要求的是最大值所以初始化为负无穷
                    for (int u = 0; u < 4; u ++ )
                    {
                        int d = distance[u];
                        if (d > d1[j][k])//若可以更新最大值
                        {
                            d2[j][k] = d1[j][k];
                            d1[j][k] = d;
                        }
                        else if (d2[j][k] < d && d < d1[j][k]) d2[j][k] = d;//若可以更新次大值,注意要严格小于最大值
                    }
                }
            }
        }
    }
}

LL lca(int a, int b, int w)
{
    if (a == b) return INF;
    if (depth[a] < depth[b]) swap(a, b);//保证a所在的位置比b深好进行下面操作
    
    int cnt = 0;
    static int distance[N * 2];//要记录次大值与最大值所以大小为2 * N;用static是为了节省空间
    for (int k = 16; k >= 0; k -- )//将a节点跳到与b同一层的深度
        if (depth[fa[a][k]] >= depth[b])//若a跳2^k的深度大于b的深度则将a跳过去
        {
            distance[cnt ++ ] = d1[a][k];//记录[a, a + 2 ^ k]路径之间的值
            distance[cnt ++ ] = d2[a][k];//要先纪律顺序不能反否则a会被更新
            a = fa[a][k];//将a跳到fa[a][k]
        }
    
    if (a != b)//说明他们跳到同一深度后还不是同一节点
    {
        for (int k = 16; k >= 0; k -- )//将a, b跳到a, b的最近公共祖先的下一层
            if (fa[a][k] != fa[b][k])
            {
                distance[cnt ++ ] = d1[a][k];//记录a, b的最大与次大值
                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 ++ ] = d2[a][0];
            distance[cnt ++ ] = d1[b][0];
            distance[cnt ++ ] = d2[b][0];
    }
    
    LL max1 = -INF, max2 = -INF;//找出最大值与次大值
    for (int i = 0; i < cnt; i ++ )
    {
        if (distance[i] > max1)//若最大值可被更新
        {
            max2 = max1;
            max1 = distance[i];
        }
        else if (max2 < distance[i] && distance[i] < max1) max2 = distance[i];//次大值可被更新
    }
    
    if (w > max1) return w - max1;//找出符合条件的w - max1的最小值然后加上main函数里的sum即为
    if (w > max2) return w - max2;//最小生成树
    
    return INF;//说明不存在可以被更新次小生成树。
}

LL kruscal()
{
    LL res = 0;
    sort(edge, edge + m);
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ ) p[i] = i;
    
    for (int i = 0; i < m; i ++ )
    {
        int a = edge[i].a, b = edge[i].b, w = edge[i].w;
        
        int fa = find(a), fb = find(b);
        
        if (fa != fb)
        {
            p[fa] = fb;
            res += w;
            edge[i].used = true;
            add(a, b, w), add(b, a, w);
        }
    }
    
    return res;
}

int main()
{
    cin >> n >> m;
    
    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        edge[i] = {a, b, c};
    }
    
    LL sum = kruscal();//求最小生成树
    
    bfs();//预处理fa, d1, d2数组
    
    LL res = 1e18;
    for (int i = 0; i < m; i ++ )
        if (!edge[i].used)//枚举分数边
        {
            int a = edge[i].a, b = edge[i].b, w = edge[i].w;
            res = min(res, sum + lca(a, b, w));//找出大于sum的最小值即次小生成树
        }
    
    cout << res << endl;
    
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值