P4180题解

P4180 严格次小生成树

题意:

求给定无向图中的严格次小生成树。

分析:

先来看一个示例:

我们首先找到图中的最小生成树(图中红线):

若加入蓝色的这条边,则绿色区域内所选边会变为一个环。由最小生成树的定义可知,蓝边的权值大于等于该环中最大的红边边权。由于要求严格次小,因此若蓝边边权等于最大红边边权,则要求绿色区域内比最大红边边权大的最小边权。

我们设蓝边的两端点分别为 \(u\)\(v\)。我们设 \(u,v\) 的最近公共祖先为 \(LCA\),然后分别求路径 \(u\rightarrow LCA\) 和 路径 \(v\rightarrow LCA\) 上的最大值和次大值。
最终答案为最小生成树的权值和-被替换的边权值+非最小生成树上的边的最大值/次大值

Code:

/*
user:xcj
time:2022.3.28
*/
#include <bits/stdc++.h>
#define int long long
#define INF 1073741823
#define N 3000010
#define M 100010
using namespace std;

int n, m, ans = 0x3fffffffffffffffLL, cnt, sum, fa[M], dep[M], minn[M][22], maxn[M][22], pnt[M][22], head[M];//maxn记录最大,minn记录次大
bool used[N];

struct xcj{
    int to, nxt, value;
} e[N];

struct xcx{
    int from, to, value;
} a[N];

inline int read(){
    int s = 0, w = 1;
    char ch = getchar();
    for (; ch < '0' || ch > '9'; w *= ch == '-' ? -1 : 1, ch = getchar());
    for (; ch >= '0' && ch <= '9'; s = s * 10 + ch - '0', ch = getchar());
    return s * w;
}

bool cmp(xcx u, xcx v){return u.value < v.value;}

void add(int u, int v, int w){e[++cnt] = {v, head[u], w}, head[u] = cnt;}

int find_f(int x){return fa[x] == x ? x : fa[x] = find_f(fa[x]);}

void kruskal(int tot = 0){//寻找最小生成树
    sort(a + 1, a + m + 1, cmp);
    for (int i = 1; i <= n; ++i) fa[i] = i;
    for (int i = 1; i <= m; ++i){
        int x = find_f(a[i].from), y = find_f(a[i].to);
        if (x != y){
            fa[x] = y, ++tot, sum += a[i].value, used[i] = 1;
            add(a[i].from, a[i].to, a[i].value);
            add(a[i].to, a[i].from, a[i].value);
        }
        if (tot == n - 1) break;
    }
}

void dfs(int x, int f){
    dep[x] = dep[f] + 1, pnt[x][0] = f, minn[x][0] = -INF;
    for (int i = 1, ptr; (1 << i) <= dep[x]; ++i){
        pnt[x][i] = pnt[pnt[x][i - 1]][i - 1];
        int kk[4] = {maxn[x][i - 1], maxn[pnt[x][i - 1]][i - 1], minn[x][i - 1], minn[pnt[x][i - 1]][i - 1]};
        sort(kk, kk + 4);
        maxn[x][i] = kk[3], ptr = 2;
        while (~ptr && kk[ptr] == kk[3]) --ptr;
        minn[x][i] = ~ptr ? -INF : kk[ptr];//取严格次大值
    }
    for (int i = head[x]; i; i = e[i].nxt){
        int y = e[i].to, z = e[i].value;
        if (y != f) maxn[y][0] = z, dfs(y, x);
    }
}

int ask(int u, int v, int w, int res = -INF){//寻找最大和次大
    for (int i = 21; ~i; --i)
        if (dep[pnt[u][i]] >= dep[v]){
            if (w != maxn[u][i]) res = max(res, maxn[u][i]);
            else res = max(res, minn[u][i]);
            u = pnt[u][i];
        }
    return res;
}

int lca(int u, int v){//倍增求LCA
    if (dep[u] < dep[v]) swap(u, v);
    for (int i = 21; ~i; --i)
        if (dep[pnt[u][i]] >= dep[v]) u = pnt[u][i];
    if (u == v) return u;
    for (int i = 21; ~i; --i)
        if (pnt[u][i] != pnt[v][i]) u = pnt[u][i], v = pnt[v][i];
    return pnt[u][0];
}

signed main(){
    n = read(), m = read();
    for (int i = 1, u, v, w; i <= m; ++i) u = read(), v = read(), w = read(), a[i] = {u, v, w};
    kruskal(), dfs(1, 0);
    for (int i = 1; i <= m; ++i)
        if (!used[i]){
            int LCA = lca(a[i].from, a[i].to), resx = ask(a[i].from, LCA, a[i].value), resy = ask(a[i].to, LCA, a[i].value);
            if (max(resx, resy) > -INF) ans = min(ans, sum - max(resx, resy) + a[i].value);//能找到该边权值
        }
    printf("%lld\n", ans == INF ? -1 : ans);
    return 0;
}
posted @ 2022-03-28 15:57  leoair  阅读(18)  评论(0)    收藏  举报