POI2004 SZP
原题链接
题意是如果我们选择了一个点
我们贪心的想到,如果一个点
另外每个点出度为
那么入度为
那么剩下的问题就是若干个环,一些点可以直接被选择(受环外点影响),问最多可以选择多少个点。随便找一个位置破环成链,讨论这个位置选或不选,取最大值即可。具体看代码
#include<stdio.h>
#include<algorithm>
#include<queue>
using namespace std;
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-48,ch=getchar();
return x*f;
}
const int MAXN=1e6+10,INF=1e9+7;
int n,a[MAXN],into[MAXN],ans;
bool vis[MAXN],b[MAXN],f[MAXN];
queue <int> q;
int dfs(int x,int pre)
{
vis[x]=true;int ans=0;
//f[x] 表示是否选择 x ,b[x]表示 x 是否可以通过环外点选择上
if(a[x]!=pre)//下一个点不是开始的点
{
if(!f[x]||b[a[x]]) f[a[x]]=1,ans+=1;//可以选择就加 1
ans+=dfs(a[x],pre);
}
else if(!(!f[x]||b[pre])&&f[pre]) ans-=1;
//如果下一个点是开始的点,并且一开始默认选择了但并不能选择的话,让当前点 x 不再被选择,所以减掉 1
f[x]=false;//回溯时候把 f[x] 重新设为 false
return ans;
}
int main()
{
n=read();
for(register int i=1;i<=n;++i) a[i]=read(),into[a[i]]++;//记录入度数
for(register int i=1;i<=n;++i) if(!into[i]) q.push(i);//入度为 0 的放入队列
while(!q.empty())
{
int x=q.front();q.pop();
vis[x]=true;if(b[x]) ans++;//如果被选择了,答案 +1
if(!b[x]) b[a[x]]=true;//当前点没被选,下一个点可以被选,一定选
if(!--into[a[x]]) q.push(a[x]);//如果入度为 0 了放入队列,这样剩下的就一定是环上的点
}
for(register int i=1;i<=n;++i)
{
if(!vis[i])//找到一个点断开环
{
f[i]=true;//f[i] 表示是否选择 i
int ans1=dfs(i,i)+1;//加上第一个点的 1
int ans2=dfs(i,i);
ans+=max(ans1,ans2);//取最优情况
}
}
printf("%d\n",ans);
return 0;
}