题意简述
给定一个数组 p p p, p [ i ] p[i] p[i]表示点 i i i的父亲,但给定的 p p p珂能有点问题,现在要你改动最少的元素个数,将 p p p修复成一颗树。
数据
n < = 200000 , 1 < = p [ i ] < = n ( for each i ) n<=200000,1<=p[i]<=n(\text{for each i}) n<=200000,1<=p[i]<=n(for each i)
思路
记录一个 r o o t root root,表示当前的根(如果是-1表示当前还没有找到根)。从 1 1 1~ n n n枚举 i i i.
如果 i = = r o o t i==root i==root,不用考虑(所以代码中没有出现)。
否则:
如果 i i i和 a [ i ] a[i] a[i]已经联通(用并查集维护联没联通),说明在加 上 i i i和 a [ i ] a[i] a[i]这条边就会出现环,这是不珂以的,所以一定要修改,如果 r o o t = = − 1 root==-1 root==−1,则将 r o o t root root设置为 i i i,然后 a [ i ] a[i] a[i]连到i上。
否则连上 i i i和 a [ i ] a[i] a[i]
代码:
#include<bits/stdc++.h>
#define N 1001000
using namespace std;
class DSU
{
public:
int Father[N],Cnt[N];
void Init()
{
for(int i=0;i<N;i++)
{
Father[i]=i;
Cnt[i]=1;
}
}
int Find(int x)
{
int r=x;
while(Father[r]!=r) r=Father[r];
while(Father[x]!=x)
{
x=Father[x],Father[x]=r;
}
return x;
}
void Merge(int x,int y)
{
int ax=Find(x),ay=Find(y);
if (Cnt[ax]<Cnt[ay])
{
Cnt[ay]+=Cnt[ax];
Father[ax]=ay;
}
else
{
Cnt[ax]+=Cnt[ay];
Father[ay]=ax;
}
}
bool Query(int x,int y)
{
return Find(x)==Find(y);
}
}D;//并查集
int n,a[N];
int root=-1;//初始设为-1
void Input()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if (i==a[i]) root=i;
//如果本来就有根,就设置为本来的根
}
}
void Solve()
{
int ans=0;
for(int i=1;i<=n;i++)
{
int u=i,&v=a[i];
//v前面一定要加'&',表明我们珂以通过v修改a[i]的值
if (u!=root)
{
if (D.Query(u,v))
{
//已经联通
ans++;
if (root==-1)
{
root=u;
//设置根
}
v=root;//换个位置连上(此时我们也修改了a[i],注意)
}
else
{
D.Merge(u,v);
//连上
}
}//u==root的情况不考虑
}
printf("%d\n",ans);
for(int i=1;i<=n;i++)
{
printf("%d ",a[i]);
//此时改完的a[i]就是正确答案
}putchar('\n');
}
main()
{
D.Init();
Input();
Solve();
return 0;
//华丽结束
}