题意:
给定长度为n的序列a, a(i)=x表示i有一条指向x的边, 如果图中只存在一个点,满足a[i]=i,那个这个点是树根, 问最少修改a序列多少个值,可以使得序列a表示一棵树。
数据范围:n<=2e5
解法:
先找到一个a[i]=i的作为根(rt=i),那么其他a[x]=x的就不能作为根了,要改成指向根(a[x]=rt)。 (但是可能不存在a[i]=i的点i,也就是找不到根,这时图中一定存在环, 遇到环的时候,将形成环的边修改为指向节点自己,并令这个点为根)。
并查集维护集合,因为树不允许存在环,当遇到环的时候,将环边改为指向根即可。
code:
#include<bits/stdc++.h>
using namespace std
;
#define int long long
const int maxm
=2e5+5;
int pre
[maxm
];
int a
[maxm
];
int n
;
int ffind(int x
){
return pre
[x
]==x
?x
:pre
[x
]=ffind(pre
[x
]);
}
signed main(){
cin
>>n
;
for(int i
=1;i
<=n
;i
++)pre
[i
]=i
;
for(int i
=1;i
<=n
;i
++)cin
>>a
[i
];
int rt
=0;
for(int i
=1;i
<=n
&&!rt
;i
++){
if(a
[i
]==i
&&!rt
)rt
=i
;
}
int ans
=0;
for(int i
=1;i
<=n
;i
++){
if(i
==rt
)continue;
if(a
[i
]==i
){
ans
++;
a
[i
]=rt
;
pre
[ffind(i
)]=ffind(rt
);
}else{
int x
=ffind(a
[i
]),y
=ffind(i
);
if(x
!=y
)pre
[x
]=y
;
else{
ans
++;
if(!rt
){
rt
=i
;
a
[i
]=i
;
}else{
a
[i
]=rt
;
}
pre
[ffind(i
)]=ffind(rt
);
}
}
}
cout
<<ans
<<endl
;
for(int i
=1;i
<=n
;i
++)cout
<<a
[i
]<<' ';
return 0;
}