CJOJ 2403 次小生成樹
Description
小 C 最近學了很多最小生成樹的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正當小 C 洋洋得意之時,小 P 又來潑小 C 冷水了。小 P 說,讓小 C 求出一個無向圖的次小生成樹,而且這個次小生成樹還得是嚴格次小的,也就是說: 如果最小生成樹選擇的邊集是 EM,嚴格次小生成樹選擇的邊集是 ES,那麼需要滿足:(value(e) 表示邊 e的權值)
這下小 C 蒙了,他找到了你,希望你幫他解決這個問題。
Input
第一行包含兩個整數N 和M,表示無向圖的點數與邊數。
接下來 M行,每行 3個數x y z 表示,點 x 和點y之間有一條邊,邊的權值爲z。
Output
包含一行,僅一個數,表示嚴格次小生成樹的邊權和。(數據保證必定存在嚴格次小生成樹)
Sample Input
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
Sample Output
11
Hint
數據範圍:
數據中無向圖無自環;
50% 的數據N≤2 000 M≤3 000;
80% 的數據N≤50 000 M≤100 000;
100% 的數據N≤100 000 M≤300 000 ;
邊權值非負且不超過 10^9 。
Source
BZOJ 1977,次小生成樹
Solution
先求出最小生成樹的大小,然後枚舉沒有選入的點如果選入後與最小生成樹的差,找到最小的差之後加入到最小生成樹中去,就是次小生成樹
在找最小生成樹的過程中,將每一個最小生成樹的點加如數組中,方便以後用他們更新次小生成樹需要替換的點(因爲次小生成數是在最小生成樹上進行修改的,所以只需要搜索最小生成樹中點可以遍歷到的點)
進行倍增搜索,更新每一次跳到的點,以及跳的過程中最大距離(d1)和次大距離(d2)-->只用在最大生成樹上修改,所以只用記錄這兩個距離,遞歸調用即可
對於每一條沒有加入最小生成樹中邊的兩個端點求他們在最小生成樹上的最近公共祖先(因爲這條邊沒有加入最小生成樹,所以在找LCA時不會通過這個邊,換句話說在這裏找到的LCA不是兩端點的任意一個),然後用他們的該LCA更新如果加入這條邊與最小生成樹中的那條與該邊某一端點相連的邊的差的最小值(可能有點繞,也不知道些明白沒,不明白就看代碼吧cal函數),然後在所有沒有加入的邊的最小值中,找一個最小值加入最小生成樹的大小中,即爲次小生成樹
Code
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 100001
#define M 300001
#define LL long long
using namespace std;
struct node {int x, y, d; bool bj;} e[M];
struct edge {int to, nxt, v;} a[N * 2];
int n, m, fa[N], cnt, head[N], deep[N], d1[N][17], d2[N][17], f[N][17], temp = 0x7fffffff, tot;
LL ans;
inline bool comp(node a, node b) {
return a.d < b.d;
}
inline int findfa(int x) {
if (x != fa[x]) return fa[x] = findfa(fa[x]);
return x;
}
inline void add(int from, int to, int dis) {
a[++cnt].to = to, a[cnt].nxt = head[from], a[cnt].v = dis, head[from] = cnt;
}
inline void dfs(int x, int fa) {
for (int i = 1; i < 17; ++i) {
if (deep[x] < (1 << i)) break;
f[x][i] = f[f[x][i - 1]][i - 1];
d1[x][i] = max(d1[x][i - 1], d1[f[x][i - 1]][i - 1]);
if (d1[x][i - 1] == d1[f[x][i - 1]][i - 1]) d2[x][i] = max(d2[x][i - 1], d2[f[x][i - 1]][i - 1]);
else {
d2[x][i] = min(d1[x][i - 1], d1[f[x][i - 1]][i - 1]);
d2[x][i] = max(d2[x][i - 1], d2[x][i]);
d2[x][i] = max(d2[x][i], d2[f[x][i - 1]][i - 1]);
}
}
for (int i = head[x]; i; i = a[i].nxt)
if (a[i].to != fa) {
f[a[i].to][0] = x, d1[a[i].to][0] = a[i].v;
deep[a[i].to] = deep[x] + 1, dfs(a[i].to, x);
}
}
inline int LCA(int x, int y) {
if (deep[x] < deep[y]) swap(x, y);
int t = deep[x] - deep[y];
for (int i = 0; i < 17; ++i)
if ((1 << i) & t) x = f[x][i];
for (int i = 16; i >= 0; i--)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
if (x == y) return x;
return f[x][0];
}
inline void cal(int x, int fa, int v) {
int mx1 = 0, mx2 = 0;
int t = deep[x] - deep[fa];
for (int i = 0; i < 17; ++i)
if ((1 << i) & t) {
if (d1[x][i] > mx1) mx2 = mx1, mx1 = d1[x][i];
mx2 = max(mx2, d2[x][i]); x = f[x][i];
}
if (mx1 != v) temp = min(temp, v - mx1);
else temp = min(temp, v - mx2);
}
inline void solve(int t, int v) {
int x = e[t].x, y = e[t].y, f = LCA(x, y);
cal(x, f, v), cal(y, f, v);
}
int main() {
freopen("2403.in", "r", stdin);
freopen("2403.out", "w", stdout);
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; ++i) scanf("%d %d %d", &e[i].x, &e[i].y, &e[i].d);
for (int i = 1; i <= n; ++i) fa[i] = i;
sort(e + 1, e + 1 + m, comp);
for (int i = 1; i <= m; ++i) {
int a = findfa(e[i].x);
int b = findfa(e[i].y);
if (a != b) {
fa[a] = b, ans += e[i].d, e[i].bj = 1;
add(e[i].x, e[i].y, e[i].d), add(e[i].y, e[i].x, e[i].d);
}
}
dfs(1, 0);
for (int i = 1; i <= m; ++i)
if (!e[i].bj) solve(i, e[i].d);
printf("%lld\n", ans + temp);
return 0;
}
Summary
一道挺玄學的題目,是參考題解寫的……