解题算法:并查集+向上找祖先
题目链接:点我点我
大体思路:先用并查集查找u v两个点的祖先是不是同一个
1、如果不是则合并两棵树。
2、如果是则表示现在能够构成环,根据u,v两个点分别向上查找其到祖先的路径,在遍历两条路径,当有公共节点的时候就表示u,v两个点的最近公共祖先,也是构成环的最开始的节点,从这里开始将两条路径保存并输出出来就可以了。
注意点:在使用并查集的find() 查找祖先时,不要缩短路径。
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <cstdio>
#include <cstring>
#define INF 0x3f3f3f3f
#define MAX_N 100100
using namespace std;
int a[MAX_N],b[MAX_N],c[MAX_N];
int pre[MAX_N];
int find(int x){
if(pre[x] == x) return x;
// 这里不能缩减路径,否则当u或v向上查找路径时就会发生错误
else return find(pre[x]);
}
int Union(int x,int y){
int fx = find(x),fy = find(y);
// fx = fy 表示同一个祖先,发现环,直接退出。
if(fx == fy) return 1;
// 定义祖先小的为父节点,祖先大的为子节点。
if(fx > fy) pre[x] = y;
else pre[y] = x;
return 0;
}
int main(){
for (int i = 0;i < MAX_N;++i){
pre[i] = i;
a[i] = b[i] = c[i] = 0;
}
int n,f = 0,uu = -1,vv;
cin >> n;
for (int i = 0;i < n;++i){
int u,v;
scanf("%d%d",&u,&v);
// f = 0 表示还没有发现环
if(f == 0)
f = Union(u,v);
// 表示刚发现环,将构成环的节点u,v保存起来。
if(f && uu == -1){
uu = u,vv = v;
}
}
int cnt1 = 0,cnt2 = 0,cnt = 0;
// 查找u节点到祖先的路径
while(pre[uu] != uu){
a[cnt1++] = uu;
uu = pre[uu];
}
// 查找v节点到祖先的路径
while(pre[vv] != vv){
b[cnt2++] = vv;
vv = pre[vv];
}
a[cnt1++] = uu;
b[cnt2++] = vv;
int i,j;
// 两重循环查找u,v的最近公共祖先。
for (i = 0;i < cnt1;++i){
int flag = 0;
for (j = 0;j < cnt2;++j){
if(a[i] == b[j]){
flag = 1;
break;
}
}
if(flag) break;
}
// 将u 到最近公共祖先的路径保存到答案数组当中,且要保存最近公共祖先。
for (int k = 0;k <= i;++k){
c[cnt++] = a[k];
}
// 将v 到最近公共祖先的路径保存到答案数组当中,不保存最近公共祖先。
for (int k = 0;k < j;++k){
c[cnt++] = b[k];
}
// 排序输出
sort(c,c+cnt);
for (i = 0;i < cnt;++i){
if(i) printf(" ");
printf("%d",c[i] );
}
printf("\n");
return 0;
}