AcWing 356. 次小生成树
AcWing 356. 次小生成树
一道蛮经典的题目,随手记录下。
原题链接:https://www.acwing.com/problem/content/description/358/
题目描述:
给定一张 N 个点 M 条边的无向图,求无向图的严格次小生成树。
设最小生成树的边权之和为sum,严格次小生成树就是指边权之和大于sum的生成树中最小的一个。
注意本题所求的次小生成树是严格次小的
题解:先用kruskal求出给定图的最小生成树,再枚举没有被选上的边,假设把这条边加上,就会形成一个环,只要把环中的最大边删去,就可以得到一颗新的树,枚举后得到最小的新树就是次小生成树。这里注意
因为是严格次小所以我们需要判断每个环中的最大边是否和新加入的边一样,所以还需要维护一个次大边。我们用lca倍增来维护就可以使时间复杂度降到nlogn
#include <stdio.h> #include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <map> #include <stack> #include <sstream> #include <set> #pragma GCC optimize(2) //#define int long long #define rush() int T;scanf("%d",&T);for(int Ti=1;Ti<=T;++Ti) #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); #define mm(i,v) memset(i,v,sizeof i); #define mp(a, b) make_pair(a, b) #define pi acos(-1) #define fi first #define se second //你冷静一点,确认思路再敲!!! using namespace std; typedef long long ll; typedef pair<int, int > PII; priority_queue< PII, vector<PII>, greater<PII> > que; stringstream ssin; // ssin << string while ( ssin >> int) const int N = 1e5 + 5, M = 6e5 + 5, mod = 1e9 + 7, INF = 0x3f3f3f3f; int n, m, t, anc; int e[M], ne[M], h[M], w[M], idx; int fa[N][20], d1[N][20], d2[N][20], depth[N]; int p[N]; inline int read(){ char c=getchar();int x=0,f=1; while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();} return x*f; } void add(int a, int b, int c) { e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx++; } struct Edge { int a, b, w; bool vis; bool operator< (const Edge &t) { return w < t.w; } }edge[M]; int find(int x) { if (p[x] != x) return p[x] = find(p[x]); else return p[x]; } ll kruskal() { ll res = 0; for (int i = 1; i <= n; ++i) p[i] = i; sort(edge + 1, edge + 1 + m); for (int i = 1; i <= m; ++i) { int a = edge[i].a, b = edge[i].b, c = edge[i].w; int pa = find(a), pb = find(b); if (pa == pb) continue; p[pa] = pb; edge[i].vis = 1; res += c; } return res; } void build() { mm(h, -1); for (int i = 1; i <= m; ++i) { if (!edge[i].vis) continue; int a = edge[i].a, b = edge[i].b, c = edge[i].w; add(a, b, c); add(b, a, c); } } void bfs() { mm(depth, 0x3f); queue<int>q; q.push(1); depth[0] = 0; depth[1] = 1; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = h[u]; ~i; i = ne[i]) { int v = e[i], z = w[i]; if (depth[v] > depth[u] + 1) { depth[v] = depth[u] + 1; fa[v][0] = u; d1[v][0] = z; d2[v][0] = -INF; q.push(v); for (int k = 1; k <= t; ++k) { anc = fa[v][k - 1]; fa[v][k] = fa[anc][k - 1]; int dis[4] = {d1[v][k - 1], d1[anc][k - 1], d2[v][k - 1], d2[anc][k - 1]}; d1[v][k] = d2[v][k] = -INF; for (int j = 0; j < 4; ++j) { if (dis[j] > d1[v][k]) { d2[v][k] = d1[v][k]; d1[v][k] = dis[j]; } else if (dis[j] != d1[v][k]){ d2[v][k] = max(d2[v][k], dis[j]); } } } } } } } int lca(int a, int b, int c) { int dd[N + N], cnt = 0; if (depth[a] < depth[b]) swap(a, b); for (int k = t; k >= 0; --k) { if (depth[fa[a][k]] >= depth[b]) { dd[++cnt] = d1[a][k]; dd[++cnt] = d2[a][k]; a = fa[a][k]; } } if (a != b) { for (int k = t; k >= 0; --k) { if (fa[a][k] != fa[b][k]) { dd[++cnt] = d1[a][k]; dd[++cnt] = d2[a][k]; dd[++cnt] = d1[b][k]; dd[++cnt] = d2[b][k]; a = fa[a][k]; b = fa[b][k]; } } dd[++cnt] = d1[a][0]; dd[++cnt] = d1[b][0]; } int dis1 = -INF, dis2 = -INF; // 最大和次大 for (int i = 1; i <= cnt; ++i) { int d = dd[i]; if (d > dis1) { dis2 = dis1; dis1 = d; } else if (d != dis1) { dis2 = max(dis2, d); } } if (c > dis1) return c - dis1; if (c > dis2) return c - dis2; return INF; } int main() { // freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取 // freopen("out.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中 n = read(); m = read(); t = log2(N) + 1; for (int i = 1; i <= m; ++i) { int a, b, c; a = read(); b = read(); c = read(); edge[i] = {a, b, c}; } ll res = kruskal(); // cout << res << '\n'; build(); bfs(); ll ans = 1e18; for (int i = 1; i <= m; ++i) { if (edge[i].vis) continue; int a = edge[i].a, b = edge[i].b, c = edge[i].w; ans = min(ans, res + lca(a, b, c)); } cout << ans << '\n'; // system("pause"); }