题意:
已知有向树的定义,除了根节点外每个节点都有且仅有一条边都指向它的父亲节点,而根节点有且仅有一条边指向自己。
现在输入一张图,每个节点都有一条边指向某个点,要求修改尽量少的边,将这张图变成一颗树。\
分析;
最终形成的树中一定只有根节点指向自己,并且除了根节点的自环外没有其他的环。
对于图中形成的一个环,我们只需要拆开一个点就能将这个环修改成一颗树。并且由于每个点只有一个父亲节点,所以原来的图中绝对不会出现两个环相连的情况。
那么只需要设定一个根节点,将其他所有的环都拆开并且指向这个根节点就可以了。
根节点设定的时候优先选取原先已经满足根节点条件的点,即其父亲为自己的点。如果这样的点不存在,任意选取一个点即可。
方法1:首先用并查集统计出所有的环的数量,并对每个环任意设定一个小的根节点。之后设定好真正的根节点然后将所有的小根节点都指向这个根节点就行了。
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int a[N],f[N],check[N];
int num;
int find(int x)
{
if (check[x]==num) return f[x]=x;
check[x]=num;
if (f[x]==x) return x;
return f[x]=find(f[x]);
}
int main()
{
int n,ans,x;
while (~scanf("%d",&n)){
memset(check,0,sizeof(check));
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++) f[i]=a[i];
for (int i=1;i<=n;i++){
num=i;
find(i);
}
ans=0;
x=0;
for (int i=1;i<=n;i++){
if (f[i]==i&&a[i]==i){
x=i;
break;
}
}
if (!x){
for (int i=1;i<=n;i++){
if (f[i]==i){
x=i;
a[i]=i;
ans++;
break;
}
}
}
for (int i=1;i<=n;i++){
if (i!=x&&f[i]==i) {
a[i]=x;
ans++;
}
}
printf("%d\n",ans);
for (int i=1;i<n;i++) {
printf("%d ",a[i]);
}printf("%d\n",a[n]);
}
return 0;
}
方法2:首先用并查集统计出所有的环的数量,并将环上拆环时需要指向根节点的点用数组记录下来,之后设定好真正的根节点然后将所有的小根节点都指向这个根节点就行了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;
int n;
int f[200005],a[200005];
int ans[200005];
int find(int x)
{
if(x==f[x]) return x;
else return f[x]=find(f[x]);
}
void Union(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
f[fx]=fy;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
f[i]=i;
int cnt=0;
int t=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if( find(a[i])==find(i) ) //图中有环
{
ans[cnt++]=i;
if(a[i]==i) //有一个根节点
{
t=find(i);
}
}
Union(i,a[i]);
}
int cnt1=0;
if(t==0) //随便找在环上的一个点当做根节点,让别的环拆开指向他
{
for(int i=1;i<=n;i++)
{
if(find(i)==find(a[i]))
{
t=find(i);
}
}
}
for(int i=0;i<cnt;i++)
{
if(a[ans[i]] != t)
{
a[ans[i]] =t;
cnt1++;
}
}
cout<<cnt1<<endl;
for(int i=1;i<=n;i++)
{
if(i==1) printf("%d",a[i]);
else printf(" %d",a[i]);
}
cout<<endl;
}