首先分析题目所给的树结构,将x与x控制的点a[x]连一条有向边,原图就变成了一个内向树森林。
这题放在了基环树的tag下,然后正解是一个树形dp,但是我思考了一下,我发现可以从内向树的最外圈一层一层往里面推进,但是转移的时候根本不需要dp,直接贪心就可以了。
对于一个入度为0的点x,由于x无法被控制,所以只能不选。那么选择x控制的节点a[x]投放一定是最优的。 那么在选择a[x]之后,a[a[x]]就不能被a[x]限制了,那么把他的度数-1,如果a[a[x]]的度数=0,那么说明他也可以去限制别人了,就把他加入待转移集合中。我们可以用一个队列来实现这个操作。
但是以上的转移完成之后,以上的贪心方法类似于拓扑,所以不适用与环,那么环上的点是不会被加入集合的!但是我们发现对于一个长度为cnt的环,可以选择投放的点为cnt/2个(隔一个选一个嘛),那么我们就可以求出每一颗内向树环的长度即可。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e6+10;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
while(ch>='0' && ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
int a[N],du[N],list[N],head,tail;
bool v[N];
int main()
{
int n=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
du[a[i]]++;
}
head=1; tail=0;
for(int i=1;i<=n;i++)
if(du[i]==0)
list[++tail]=i;
int ans=0;
while(head<=tail)
{
int x=list[head];
if(!v[x] && !v[a[x]])
{
ans++;
v[a[x]]=1;
du[a[a[x]]]--; if(du[a[a[x]]]==0) list[++tail]=a[a[x]];
}
v[list[head]]=1; head++;
}
int cnt=0,j;
for(int i=1;i<=n;i++)
{
if(!v[i])
{
cnt=0;
j=i;
while(a[j]!=i)
{
v[j]=1;
cnt++;
j=a[j];
}
v[j]=1;
ans+=(cnt+1)/2;
}
}
printf("%d\n",ans);
return 0;
}