题意
-
给出一组数表示每个节点的父节点,要修改最少的边使得这些节点变成一棵树
思路
-
根据题意,不符合要求的有两种可能,图中有环路,或者树根不止一个。
用并查集进行分类,将相连的节点划分到一个集合,随机指派一个符合要求的根节点(其父节点为自己)为最终的root,将其他集合若没有环路,则直接将其根节点指向root。若有环,则在环中找一个节点将其父节点修改为root。寻找环的方法:如果其父节点不是自己但是其所在集合的代表元素是它,那么就修改它的父节点就好了。
代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int fa[200005];
int p[200005];
int find(int v){
return fa[v] = fa[v] == v ? v : find(fa[v]);
}
void merge(int a, int b)
{
int x = find(a); int y = find(b);
fa[x] = y;
}
int main()
{
scanf("%d",&n);
int ans = 0;
int root = -1;
for(int i = 1; i <= n; ++i){
scanf("%d",&p[i]);
fa[i] = i;
if(p[i] == i && root == -1)root = i;
}
for(int i = 1; i <= n; ++i){
merge(i,p[i]);
}
for(int i = 1; i <= n; ++i){
if(root == -1 && p[i] == i){
root = i;
} else if( i == find(i) && p[i] != i){
ans ++;
p[i] = root == -1 ? i : root;
if(p[i] == i)root = i;
merge(i,root);
} else if(p[i] == i && i != root){
ans++;
p[i] = root;
merge(i,root);
}
}
printf("%d\n",ans);
printf("%d",p[1]);
for(int i = 2; i <= n; ++i){
printf(" %d",p[i]);
}
puts("");
return 0;
}