输入样例:
5
0
4
1
5
4
输出样例:
2
样例解释
奶牛 1 不发送任何信息,因此不会陷入循环。
奶牛 3 发送信息给奶牛 1,因此也不会陷入循环。
其他三头奶牛发送的信息都会陷入循环。
并查集 解法
利用并查集做(并查集的同一集合特性,子节点更新都指向根节点降低查找复杂度)
因为每个牛只能单项传播,故能够成一个或多个图,若一个图中存在循环,则此图中所有牛都会陷入循环,否则一定为一棵树
因此可以利用并查集的特性解决
- 并查集中若当前集合不存在循环,则所有点的根节点一定为0的子节点
- 若并查集的此集合中存在循环图,则一定能根找到自己,每一次找到根就直接将自己指向根节点(如此缩短并查集判断的复杂度)
- (注意由于每个牛最多传递一个牛,故一个图中最多一个循环环)
- 因此在找到循环的根节点后,根节点应该是指向自己的,记录下后指向0变为树(此树都存在循环)
#include <stdio.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
using namespace std;
int main(){
int n;
cin>>n;
int arr[n+1] = {0}; // 存储当前牛的祖辈节点,初始化每个牛都为根节点
vector<int> loop; // 存储循环并查集的root
for(int i = 1; i <= n; i++){
// 若当前节点为根节点,则父节点是0
// 每次记录祖宗节点都循环找到根节点记录下来
// 若找到祖宗节点为自己,则设自己为根,并记录到循环数组中
cin>>arr[i];
if(arr[i]){ // 当前节点非根节点
while(arr[arr[i]]){
arr[i] = arr[arr[i]]; // 直到找到爷爷节点为0,则父节点为根节点
if(i == arr[i]){
loop.push_back(i);
arr[i] = 0; // 改变根,恢复为正常树,此树
break;
}
}
}
}
int ans = loop.size(); // 3个根节点
for(int i = 0;i<loop.size();i++){
// cout<<loop[i]<<" ";
for(int j = 1;j<=n;j++){
while(arr[arr[j]] and arr[j] != loop[i]){
arr[j] = arr[arr[j]];
}
if(arr[j] == loop[i]){
ans++;
// cout<<j<<" ";
}
}
// cout<<endl;
}
cout<<n-ans<<endl;
return 0;
}