题意
有 n 个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 T[i] 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?
思路
显然一个人能够得知自己的生日的前提是他的生日经过重重传递后又传递到了他耳里,即这个以这些人为点,它们与它们的信息传递对象所组成的边构成的图内存在环,而环的长度的最小值即为这个游戏的轮数。
问题转化为“每个点的出度均为1的图中环的长度的最小值”,那么我们就要知道什么样的时候能组成环,可以想到并查集(我们将环的第一个点给环作为它的代号,此时这个环可以看作以这个点为父节点的并查集),而这个环的长度可以比拟为父节点的生日先跑到了一个节点,然后从一个节点跑到另一个节点,最后再跑回来,那么加起来就是 两个节点到父节点的距离之和 + 1(两个节点之间的距离是1) 。
现在还差一个问题:怎么得到点到父节点之间的距离?如果因为父节点不相同要接入别的父节点时直接用另一个点的距离+1刷新;而当寻找父节点时因为会直接刷新父节点,所以需要用一个变量来记录旧的父节点,然后子节点到父节点的距离就是旧父节点到父节点的距离+子节点到旧父节点的距离了!
思路已经明朗,代码开始实现!
代码
#include <iostream>
#define MAXN 200005
using namespace std;
int n, T[MAXN], f[MAXN], dist[MAXN];
int ans = 888888888;
int find_f(int x) {
if (f[x] == x)
return x;
int oldFather = f[x];
f[x] = find_f(f[x]);
dist[x] += dist[oldFather];
return f[x];
}
void init() {
for (int i = 1; i <= n; ++i)
f[i] = i;
}
void solve() {
cin >> n;
init();
for (int i = 1; i <= n; ++i) {
cin >> T[i];
int uu = find_f(i);
int vv = find_f(T[i]);
if (uu != vv) {
f[uu] = vv;
dist[i] = dist[T[i]] + 1;
} else {
ans = min(ans, dist[i] + dist[T[i]] + 1);
}
}
cout << ans;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}