倍增最小公共祖先LCA 次小生成树 Kruscal扩展 BFS 并查集 最大值次大值 T5 356. 次小生成树 - AcWing题库

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

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

输入格式

第一行包含两个整数

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

输出格式

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

数据范围

,
,

输入样例:

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

输出样例:

11

思路

求次小生成树问题, 之前做过一道秘密的牛奶运输, 思路是先求出最小生成树, 然后枚举非树边, 删去 之间的最大边 , 得出次小生成树

其时间复杂度是 , 第一项是枚举非树边, 第二项是预处理, 第三项是Kruscal算法。

而该题的范围是, 一项就不能通过, 需要优化找到 之间最大边的操作。且这里要求严格次小生成树, 还需要记录次大边。

对一个树中求两个点之间的最大边和次大边, 参考祖孙询问一题, 复杂度预处理每个点到根节点的距离 , 而 之间的距离就是 , 的最小公共祖先。

预处理 点跳 步后的点编号。 而这里的最大边次大边则需要再加两个数组, 分别为 , 代表 点到跳 步后的点之间的最大边/最小边权值。

处理完毕后, 假如要求 之间的最大边和次大边: 这段路径上的最大边和次大边就是在一堆 d1,d2 中取得。可以定义一个静态数组 存下所有的边权(因为倍增的做法, 我们只会存和遍历 个), 存完之后再遍历求解即可。

而这个 求的时候也跟 类似: 同样存下这些值然后求解即可。

代码流程:

  1. 求一遍最小生成树, 得出权值和 , 并把最小生成树建图
  2. 对图进行遍历, 预处理 数组, 要保证
  3. 枚举所有非树边, 判断是否跟最大边 相等, 若相等则替换 , 否则就替换
  4. 更新

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <queue>
#include <cmath>
using namespace std;
const int N = 1e5 +10, M = 3e5 + 10, INF = 0x3f3f3f3f;
typedef long long LL;
int h[N], e[M], ne[M], w[M], idx;
int p[N];
int f[N][17], d1[N][17], d2[N][17];
int depth[N], q[N];
struct Edge
{
    int a, b, c;
    bool used;
    bool operator <(const Edge &W) const {
        return c < W.c;
    }
}edge[M];
int n,m;
 
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 p[x] == x? p[x] : p[x] = find(p[x]);}
 
LL kruscal()
{
    memset(h, -1, sizeof h);
    idx = 0;
    LL res = 0;
    for(int i = 1; i <= n; i++) p[i] = i;
    sort(edge, edge + m);
    for(int i = 0; i < m; i++)
    {
        int a = edge[i].a, b = edge[i].b, c = edge[i].c;
        int pa = find(a), pb = find(b);
        if(pa != pb)
        {
            p[pb] = pa;
            res += c;
            add(a, b, c), add(b,a,c);
            edge[i].used = true;
        }
    }
    
    return res;
}
 
 
void bfs()
{
    memset(depth, 0x3f, sizeof depth);
    int hh = 0, tt = 0;
    depth[0] = 0, depth[1] = 1;
    q[0] = 1;
    while(hh <= tt)
    {
        int t = q[hh++];
        for(int i = h[t]; ~i ; i = ne[i])
        {
            int j = e[i];
            if(depth[j] > depth[t] + 1)
            {
                depth[j] = depth[t] + 1;
                q[++tt] = j;
                f[j][0] = t;
                d1[j][0] = w[i], d2[j][0] = -INF;
                for(int k = 1; k < 17; k++)
                {
                    int anc = f[j][k-1];
                    f[j][k] = f[anc][k-1];
                    int distance[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 = distance[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 c)
{
    static int distance[N*2];
    int cnt= 0;
    if(depth[a] < depth[b]) swap(a,b);
    for(int k = 16; k >= 0; k--)
        if(depth[f[a][k]] >= depth[b])
        {
            distance[cnt++] = d1[a][k];
            distance[cnt++] = d2[a][k];
            a = f[a][k];
        }
    if(a != b)
    {
        for(int k = 16; k >= 0; k--)
            if(f[a][k] != f[b][k])
            {
                distance[cnt++] = d1[a][k];
                distance[cnt++] = d2[a][k];
                distance[cnt++] = d1[b][k];
                distance[cnt++] = d2[b][k];
                a = f[a][k];
                b = f[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(c > dist1) return c - dist1;
    else if(c > dist2) return c - dist2;
    return INF;
}
 
int main()
{
    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 = kruscal();
 
    // 预处理 f, d1, d2
    bfs();
    
    // 枚举非树边
    LL res = 1e18;
    for(int i = 0; i < m; i++)
        if(!edge[i].used)
        {
            int a = edge[i].a, b = edge[i].b, c = edge[i].c;
            res = min(res, sum + lca(a,b, c));
        }
    cout << res << endl;
    
    return 0;
}