CF698B Fix a Tree
首先这是一坨内向基环树森林。
每次操作修改一条边,可以合并两个连通块。连通块用并查集维护,如果新加入的边两端点在同一连通块内,说明加上以后会有环,需要更改这条边。
首先在原数组中找到一个 $a_i=i$ 的点钦定它是根。然后一切要改的边全部无脑往根上连即可。
如果在原数组中找不到 $a_i=i$ 的点怎么办呢,我们找到第一条需要改的边 $i\to a_i$,把它的出发点 $i$ 钦定为根。这样就可以愉快地使用并查集维护了。
简短的代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 1;
int n;
int a[maxn];
int fa[maxn];
int cnt, root;
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
fa[i] = i;
if (!root && a[i] == i) root = i;
}
for (int i = 1; i <= n; i++) {
if (i == root) continue;
int u = find(i), v = find(a[i]);
if (u != v) fa[u] = v;
else if (!root) root = i, a[i] = i, ++cnt;
else a[i] = root, fa[u] = find(root), ++cnt;
}
cout << cnt << endl;
for (int i = 1; i <= n; i++) {
cout << a[i] << " ";
}
return 0;
}