CodeForces 698B Fix a Tree

并查集,构造。

先看一下图的特殊性,按照这种输入方式,一个点的入度最多只有$1$,因此,问题不会特别复杂,画画图就能知道了。

如果给出的序列中已经存在$a[i]=i$,那么随便取一个$a[i]=i$的$i$作为$root$,剩下的每一条边$a[i] o i$,可以用并查集来处理,如果发现某条边$a[i] o i$加入前$a[i]$与$i$已经在同一集合中,说明再加$a[i] o i$会导致成环,因此将$i$的$father$改成$root$即可,并将$i$与$root$合并。

如果给出的序列中不存在$a[i]=i$,和上面的处理方法类似,唯一不同的是:找到第一条不能加入的边$a[i] o i$,将$i$的$father$改为$i$,并且$root$设置为$i$,之后出现不能加入的边和上面处理方法一样。

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<iostream>
using namespace std;
typedef long long LL;
const double pi=acos(-1.0),eps=1e-8;
void File()
{
    freopen("D:\in.txt","r",stdin);
    freopen("D:\out.txt","w",stdout);
}
template <class T>
inline void read(T &x)
{
    char c = getchar(); x = 0;while(!isdigit(c)) c = getchar();
    while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar();  }
}

const int maxn=200010;
int f[maxn],a[maxn],n;

int Find(int x)
{
    if(x!=f[x]) f[x]=Find(f[x]);
    return f[x];
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int root=-1; for(int i=1;i<=n;i++) if(a[i]==i) root=i;
    int num=0; for(int i=1;i<=n;i++) f[i]=i;

    for(int i=1; i<=n; i++)
    {
        if(i==root) continue;
        int fx=Find(i),fy=Find(a[i]);
        if(fx!=fy) f[fx]=fy;
        else
        {
            if(root==-1) a[i]=i, root=i, num++;
            else
            {
                a[i]=root, num++;
                fx=Find(root); fy=Find(i);
                f[fx]=fy;
            }
        }
    }
    printf("%d
",num);
    for(int i=1; i<=n; i++) printf("%d ",a[i]);
    printf("
");
    return 0;
}
原文地址:https://www.cnblogs.com/zufezzt/p/5801469.html