其他分享
首页 > 其他分享> > AcWing 356. 次小生成树

AcWing 356. 次小生成树

作者:互联网

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

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

输入格式: 第一行包含两个整数$N$和$M$。 接下来$M$行,每行包含三个整数$x,y,z$,表示点$x$和点$y$之前存在一条边,边的权值为$z$。

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

数据范围 :$N\leqslant 10^5,M\leqslant 3\times10^5$。

分析:我们先求出原图的最小生成树,然后枚举每一条非树边,看看当前两点构成的路径上最大边与次大边与该非树边的大小关系,由于求出的最小生成树是选出的最短边,因此非树边一定满足大于等于这两点路径上的所有边,当然也满足大于等于最大边。若等于最大边,则我们将次大边替换成非树边,否则替换最大边为非树边。树上两点路径上的最大边与次大边可以在求$\mathrm{LCA}$时也求出来,具体地,我们设$g[x][y][0]$表示点$x$向上走$2^y$步中的次大边,$g[x][y][1]$表示点$x$向上走$2^y$步中的最大边。那么我们可以先求出走到$2^{y-1}$点的最大边,然后再走$2^{y-1}$步,对这两步中的最大边取最大值即可;对于次大边,如果这两个模块内的最大边相等,则我们用这两个模块的次大边更新;如果第一阶段的最大边$>$第二阶段的最大边,那么最大边在第一阶段的次大边与第二阶段的最大边中取最大值,不然则反之。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <climits>
using namespace std;
using LL = long long;
const int N = 1e5+10, M = 3e5+10;
struct Node{
    int x, y, z;
    bool operator<(const Node& t)const{
        return z<t.z;
    }
}edge[M];
int n, m, p[N];
int h[N], e[N*2], ne[N*2], w[N*2], idx;
int f[N][18], g[N][18][2], d[N];
bool st[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 x==p[x]?x:p[x] = find(p[x]);
}

void merge(int c[2], int a[2], int b[2]){
    //a[0], b[0]分别为两段的最大值,a[1],b[1]分别为两段的次大值
    //a[0]>a[1], b[0]>b[1]
    if(a[0] == b[0])c[1] = max(a[1], b[1]);
    else if(a[0]>b[0])c[1] = max(a[1], b[0]);
    else c[1] = max(a[0], b[1]);
    c[0] = max(a[0], b[0]);
}

void bfs(int u){
    d[u] = 1;
    queue<int> q;
    q.push(u);
    
    while(q.size()){
        auto t = q.front();
        q.pop();
        
        for(int i=h[t]; ~i; i=ne[i]){
            int j = e[i];
            if(!d[j]){
                d[j] = d[t] + 1;
                q.push(j);
                f[j][0] = t;
                g[j][0][0] = w[i];
                for(int k=1; k<=17; k++){
                    f[j][k] = f[f[j][k-1]][k-1];
                    merge(g[j][k], g[j][k-1], g[f[j][k-1]][k-1]);
                }
            }
        }
    }
}

int lca(int res[], int x, int y){
    res[0] = res[1] = 0;
    if(d[x]<d[y])swap(x, y);
    for(int i=17; i>=0; i--){
        if(d[f[x][i]]>=d[y]){
            merge(res, res, g[x][i]);
            x = f[x][i];
        }
    }
    if(x==y) return x;
    for(int i=17; i>=0; i--){
        if(f[x][i] != f[y][i]){
            merge(res, res, g[x][i]);
            merge(res, res, g[y][i]);
            x = f[x][i], y = f[y][i];
        }
    }
    merge(res, res, g[x][0]);
    merge(res, res, g[y][0]);
    return f[x][0];
}


int main(void){
    cin>>n>>m;
    memset(h, -1, sizeof h);
    for(int i=0; i<m; i++)scanf("%d %d %d", &edge[i].x, &edge[i].y, &edge[i].z);
    sort(edge, edge+m);
    for(int i=0; i<m; i++)p[i] = i;
    LL res = 0;
    for(int i=0; i<m; i++){
        int x = find(edge[i].x), y = find(edge[i].y), z = edge[i].z;
        if(x != y){
            p[x] = y;
            res += z;
            st[i] = true;
            add(edge[i].x, edge[i].y, z);
            add(edge[i].y, edge[i].z, z);
        }
    }
    
    bfs(1);
    int delta = INT_MAX;
    for(int i=0; i<m; i++){
        if(!st[i]){//选择非树边
            int x = edge[i].x, y = edge[i].y, z = edge[i].z, res[2];
            lca(res, x, y);
            if(z == res[0])delta = min(delta, z-res[1]);//树边的最长边等于当前的非树边
            else if(res[1])delta = min(delta, z-res[0]);
        }
    }
    cout<<res + delta<<endl;
    return 0;
}

 

标签:merge,int,res,大边,生成,356,include,AcWing
来源: https://www.cnblogs.com/Hibert/p/15613131.html