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;
}
posted @ 2022-12-22 19:42  TernaKagiri  阅读(8)  评论(0编辑  收藏  举报  来源