利用并查集求解有向图的最小环:
首先先建图,举个栗子:
按照:
1 --> 2
2 --> 4
3 --> 2
4 --> 3
5 --> 1
这样建立一个有向图。
首先设置两个数组:
int pre[MAX_V] //查找根节点
int dis[MAX_V] //d[ i ] 定义为到根节点
发下代码吧,看着代码好理解点:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
int pre[200000 + 10]; //查找根节点
int dis[200000 + 10]; //d[ i ] 定义为到根节点
int ans = 0x3f3f3f3f;
int n;
int find_(int n){//找父结点
if(pre[n] != n){
int last = pre[n];
pre[n] = find_(pre[n]);
dis[n] += dis[last];
printf("pre[%d] = %d\n", n, pre[n]);
printf("n == %d d[%d] == %d\n", n, n, dis[n]);
printf("last == %d d[%d] == %d\n\n", last, last, dis[last]);
}
return pre[n];
}
void join(int x, int y)
{
int fx = find_(x), fy = find_(y);
if(fx != fy){
pre[fx] = fy;
dis[x] = dis[y] + 1;//x到y的距离为 1 加上 y 到其根节点的距离。
printf("fx != fy :: %d %d 的老大分别是 %d %d\n", x, y, fx, fy);
printf("合并之后的关系是:: pre[%d] == %d\n", fx, fy);
printf("d[%d] == %d d[%d] == %d\n\n", x, dis[x], y, dis[y]);
}
else{ //当根节点指向其子孙节点时,其实就是相当于构成了环。
ans = min(ans, dis[x] + dis[y] + 1);//x到y的距离为 1 加上(防止因为根节点选错,故把d[x] d[y] 都加上,这样比较保险,因为根节点的距离为0 所以不会影响最终的结果)
printf("当fx == fy时 :: min(%d , %d)\n\n", ans, dis[x] + dis[y] + 1);
}
return ;
}
int main()
{
cin>>n;
for(int i = 1; i <= n; i++) pre[i] = i;
fill(dis, dis + n, 0);
for(int i = 1; i <= n; i++){
int temp;
scanf("%d", &temp);
printf("当把%d的信息告诉%d时:\n", i, temp);
join(i, temp);
cout<<endl;
printf("********************************************************************\n");
printf("********************************************************************\n");
printf("********************************************************************\n");
cout<<endl;
}
printf("%d\n", ans);
return 0;
}
(注:建议大家可以把这个代码跑一遍,自己跟着它输出的结果走一遍,应该就全明白了!!)
代码运行结果:
5
2 4 2 3 1
当把1的信息告诉2时:
fx != fy :: 1 2 的老大分别是 1 2
合并之后的关系是:: pre[1] == 2
d[1] == 1 d[2] == 0
********************************************************************
********************************************************************
********************************************************************
当把2的信息告诉4时:
fx != fy :: 2 4 的老大分别是 2 4
合并之后的关系是:: pre[2] == 4
d[2] == 1 d[4] == 0
********************************************************************
********************************************************************
********************************************************************
当把3的信息告诉2时:
pre[2] = 4
n == 2 d[2] == 1
last == 4 d[4] == 0
fx != fy :: 3 2 的老大分别是 3 4
合并之后的关系是:: pre[3] == 4
d[3] == 2 d[2] == 1
********************************************************************
********************************************************************
********************************************************************
当把4的信息告诉3时:
pre[3] = 4
n == 3 d[3] == 2
last == 4 d[4] == 0
当fx == fy时 :: min(3 , 3)
********************************************************************
********************************************************************
********************************************************************
当把5的信息告诉1时:
pre[2] = 4
n == 2 d[2] == 1
last == 4 d[4] == 0
pre[1] = 4
n == 1 d[1] == 2
last == 2 d[2] == 1
fx != fy :: 5 1 的老大分别是 5 4
合并之后的关系是:: pre[5] == 4
d[5] == 3 d[1] == 2
********************************************************************
********************************************************************
********************************************************************
3
大家跟着走一遍之后应该理解了,那就做一道练习题试试吧~
题目链接:https://www.luogu.org/problemnew/show/P2661
题解:
该题目是说信息互相传递,看看传递几次可以传回到自己这里,其实就是一个有向图,它的每一条边可以看成流向,游戏玩几轮,就相当于最快流向自己经历几个回合,也就转换成了求有向图中最小环的边数。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
int pre[200000 + 10]; //查找根节点
int dis[200000 + 10]; //d[ i ] 定义为到根节点
int ans = 0x3f3f3f3f;
int n;
int find_(int n)
{
if(pre[n] != n){
int last = pre[n];
pre[n] = find_(pre[n]);
dis[n] += dis[last];
}
return pre[n];
}
void join(int x, int y)
{
int fx = find_(x), fy = find_(y);
if(fx != fy){
pre[fx] = fy;
dis[x] = dis[y] + 1;//x到y的距离为 1 加上 y 到其根节点的距离。
}
else{ //当根节点指向其子孙节点时,其实就是相当于构成了环。
ans = min(ans, dis[x] + dis[y] + 1);
//x到y的距离为 1 加上(防止因为根节点选错,故把d[x] d[y] 都加上,这样比较保险,因为根节点的距离为0 所以不会影响最终的结果)
}
return ;
}
int main()
{
cin>>n;
for(int i = 1; i <= n; i++) pre[i] = i;
fill(dis, dis + n, 0);
for(int i = 1; i <= n; i++){
int temp;
scanf("%d", &temp);
join(i, temp);
}
printf("%d\n", ans);
return 0;
}