题解:P16071 [ICPC 2023 NAC] Broken Minimum Spanning Tree
第一问比较简单。因为一次交换只能交换走 \(1\) 个不是最小生成树的原树边,所以答案就是原树里不属于最小生成树的边数。
第二问相比第一问就要保证每一次交换都要是生成树。我们考虑先将边分类:
- \(-1\):代表这条边属于最小生成树但不在当前树。
- \(1\):代表这条边不属于最小生成树但在当前树。
- \(2\):代表这条边属于最小生成树也在当前树。
- \(0\):代表这条边不属于最小生成树也不在当前树。
我们很容易发现 \(2\) 和 \(0\) 是不用动的,需要的只有 \(1\) 和 \(-1\)。
我们每一次可以任意选择一条为 \(1\) 的边去除变为 \(0\),然后我们就发现树就被分割成两个连通块,此时我们要找的就是两点在不同连通块的 \(-1\) 的边。因为 \(n\leqslant2000,m\leqslant3000\),所以可以直接现把所有在当前树的边通过并查集合并,然后枚举即可。
证明显然。
#include <bits/stdc++.h>
using namespace std;
const int N = 2005;
int n, m;
tuple<int, int, int, int, int> e[3005];
int f[N], sz[N];
int getf(int x) {
return f[x] == x ? x : f[x] = getf(f[x]);
}
void merge(int x, int y) {
x = getf(x), y = getf(y);
if(sz[x] > sz[y]) swap(x, y);
if(x == y) return;
f[x] = y;
sz[y] += sz[x];
}
void init() {
for(int i = 1; i <= n; i++) f[i] = i, sz[i] = 1;
}
void kruskal() {
init();
sort(e + 1, e + 1 + m, [](tuple<int, int, int, int, int> a, tuple<int, int, int, int, int> b) {
if(get<2>(a) == get<2>(b)) return get<3>(a) < get<3>(b);
else return get<2>(a) < get<2>(b);
});
int cnt = 0;
for(int i = 1; i <= m; i++) {
if(getf(get<0>(e[i])) == getf(get<1>(e[i]))) {
if(get<3>(e[i]) > n - 1) get<4>(e[i]) = 0;
else get<4>(e[i]) = 1;
continue;
}
merge(get<0>(e[i]), get<1>(e[i]));
if(get<3>(e[i]) > n - 1) get<4>(e[i]) = -1, cnt++;
else get<4>(e[i]) = 2;
}
cout << cnt << "\n";
for(int i = 1; i <= m; i++) if(get<4>(e[i]) == 1) {
init();
get<4>(e[i]) = 0;
for(int j = 1; j <= m; j++) if(get<4>(e[j]) >= 1) merge(get<0>(e[j]), get<1>(e[j]));
for(int j = 1; j <= m; j++) if(get<4>(e[j]) == -1 && getf(get<0>(e[j])) != getf(get<1>(e[j]))) {
cout << get<3>(e[i]) << " " << get<3>(e[j]) << "\n";
get<4>(e[j]) = 2;
break;
}
}
}
int main() {
cin >> n >> m;
for(int i = 1, u, v, w; i <= m && cin >> u >> v >> w; i++)
e[i] = {u, v, w, i, 0};
kruskal();
return 0;
}

浙公网安备 33010602011771号