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;
}
浙公网安备 33010602011771号