这题大概是给一个每个点出度都为1的图,求最小的环的长度。
我的思路是直接对每一个点深搜,搜到搜过的点就说明存在环,更新答案。
int n; // 点数
int p[maxn]; // 传递对象编号
bool vis[maxn]; // 在一次搜索中访问过的点
int dis[maxn]; // 该点到本次搜索起点的距离
int ans = INF; // 最小环长度
void dfs(int s, int depth)
{
if (vis[s]) // 存在环
{
ans = min(ans, depth - dis[s]); // 更新答案
}
else // 不存在环就继续搜索
{
vis[s] = true;
dis[s] = depth;
dfs(p[s], depth + 1);
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &p[i]);
}
for (int i = 1; i <= n; i++)
{
memset(vis, 0, sizeof(vis));
memset(dis, 0, sizeof(dis));
dfs(i, 0); // 从每个点开始搜索
}
printf("%d\n", ans);
return 0;
}
但是这样显然太暴力了,重复计算了很多点,那么怎么优化呢?
这里可以再开一个数组,记录已经搜索过的点。注意:上面的vis是记录一次搜索内搜过的点,是为了找到环,而这个数组则是用来避免重复的。
/* 增加的部分 */
bool nvis[maxn];
void dfs(int s, int depth)
{
if (nvis[s]) //之前搜过的
{
return;
}
if (vis[s]) // 存在环
{
ans = min(ans, depth - dis[s]); // 更新答案
}
else // 不存在环就继续搜索
{
vis[s] = true;
dis[s] = depth;
dfs(p[s], depth + 1);
}
nvis[s] = true; /* 注意这里要在回溯的时候标记,不能在前面标记,
不然检测不到环。*/
}
/* 主函数在搜索前加一个判断 */
if (nvis[i])
{
continue;
}
但是,尽管去掉了重复的点,提交之后还是会超时,这是因为每一次搜索的时候都要把vis和dis数组重新赋值为0,消耗了大量的时间。分析之后我们发现,vis和dis数组在搜索中虽然起局部变量的作用,但是每次搜索完之后这一部分由于nvis已经排除了重复的,所以本次搜索用到的部分在后面都不会有影响,即对于其他的搜索来说,可用的vis和dis都为0。因此,不需要每次搜索前都重新赋值。
完整的代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 200005;
const int INF = 0x3F3F3F3F;
int n; // 点数
int p[maxn]; // 传递对象编号
bool vis[maxn]; // 在一次搜索中访问过的点
int dis[maxn]; // 该点到本次搜索起点的距离
int ans = INF; // 最小环长度
void dfs(int s, int depth)
{
if (nvis[s]) //之前搜过的
{
return;
}
if (vis[s]) // 存在环
{
ans = min(ans, depth - dis[s]); // 更新答案
}
else // 不存在环就继续搜索
{
vis[s] = true;
dis[s] = depth;
dfs(p[s], depth + 1);
}
nvis[s] = true; /* 注意这里要在回溯的时候标记,不能在前面标记,
不然检测不到环。*/
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &p[i]);
}
for (int i = 1; i <= n; i++)
{
if (nvis[i])
{
continue;
}
// memset(vis, 0, sizeof(vis));
// memset(dis, 0, sizeof(dis));
dfs(i, 0); // 从每个点开始搜索
}
printf("%d\n", ans);
return 0;
}