B. Fix a Tree
去年暑假集训时候做的题,一下子没AC,看着它躺在CF右边列表里强迫症又犯了。
题意:给你n个点,每个点有一个父亲节点p[i],代表i与p[i]相连,问最少改变多少个点的p[i]使其变成一棵树。
看样例大概就能猜出来是并查集,确实,去年也是这样想的,不过代码却写的极其丑陋。今天再看,发现只有两种情况需要讨论,一种是孤立点,一种是成自环的情况,解决这两种情况就行了,我们把两种情况分开考虑, 由于要求最少改变,孤立点必然合并成一个集合,各个环也会合并成一个,那么两种集合再进行合并,我们将环的根的父节点连在最后的孤立点上即可,因为我们不用改变孤立点的父节点,其父节点就是本身,而环中的父节点无论如何都要被改变。
int f[N],p[N],pp[N];
int vis1[N],vis2[N];
int find(int x)
{
return f[x]==x?x:f[x]=find(f[x]);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
memset(vis1,0,sizeof(vis1));
memset(vis2,0,sizeof(vis2));
for(int i=1; i<=n; i++) f[i]=i;
int ans=0,k1=0,k2=0,fa;
for(int i=1; i<=n; i++)
{
scanf("%d",&p[i]);
pp[i]=p[i];
int f1=find(i),f2=find(p[i]);
if(f1==f2)//成环或者成独立集
{
if(p[i]==i) vis1[k1++]=i;
else vis2[k2++]=i;
p[i]=i;
}
f[f1]=f2;
}
ans=k2;
if(k1)
{
ans+=k1-1;
fa=find(vis1[0]);
for(int i=1; i<k1; i++)
{
int x=find(vis1[i]);
p[vis1[i]]=vis1[0];
f[x]=fa;//合并独立集
}
}
fa=vis2[0];
for(int i=1; i<k2; i++)
{
int x=find(vis1[i]);
p[vis2[i]]=vis2[0];
f[x]=fa;//合并自环
}
if(k1&&k2) p[vis2[0]]=vis1[0];//要保证改变的点最少,所以vis1中保存的独立点尽量不变
printf("%d\n",ans);
for(int i=1; i<=n; i++)
printf("%d%c",p[i],i==n?'\n':' ');
}
return 0;
}
//6
//2 3 1 4 6 6