https://www.luogu.org/fe/problem/P2661
一个含有n个点的图,图中每个点的出度为1(如果不考虑自环,那么有可能出现出度为0的情况)要求寻找图中最小的环。
做法:
在读取边时去除形成自环的边。然后遍历所有点,删除所有入度为0的点(删除一个点时它通往的下一个节点入度也相应减1,所以如果下一个节点入度变为0,那么也要删除)。然后剩余的所有点一定都属于某一个环的一部分。遍历所有的环,找到最小的环即可。
注意合理删点,并且避免重复遍历(所有在环上的点只需要被遍历一遍,遍历过的点不必再看)我先是因为只从入度判断一个点是否被删除,结果被卡内存(递归层数太多?),后来发现应当加一个标记数组,直接标记一个点是否有效。每次只删除入度为0且被标记为有效的点。删除操作就是把这个点标记为无效,将它抵达的下一个点入度减1。删点操作本身也是一个dfs,是否删除下一个点也要同时满足两个要求:1是下一个点入度为0,2是下一个点的标记仍然处于有效状态。只判断下一个点入度为0就去删除的话会导致重复删点,导致爆内存。
代码如下:
#include <bits/stdc++.h>
using namespace std;
int n;
int way[200000+1];
int ans = 0x7f7f7f7f;
int beginn;
int rd[200000+1];
bool isok[200000+1];
void dfs(int p,int t)
{
isok[p] = 0;
if(way[p] == beginn)
{
if(t < ans)
{
ans = t;
}
return;
}
else
{
dfs(way[p],t+1);
}
}
void deletedfs(int p)
{
isok[p] = 0;
rd[way[p]] --;
if(rd[way[p]] <= 0&&isok[way[p]] == 1)
{
deletedfs(way[p]);
}
}
int main()
{
scanf("%d",&n);
memset(rd,0,sizeof(rd));
memset(isok,1,sizeof(isok));
for(int i = 1; i<=n; i++)
{
scanf("%d",way+i);
if(way[i]!=i)
rd[way[i]]++;
}
for(int i = 1; i<=n; i++)
{
if(rd[i] == 0&&isok[i] == 1)
{
deletedfs(i);
}
}
for(int i = 1; i<=n; i++)
{
if(isok[i] == 1)
{
beginn = i;
dfs(i,1);
}
}
printf("%d",ans);
return 0;
}