第八届蓝桥杯决赛——发现并输出环

解题算法:并查集+向上找祖先

题目链接:点我点我

大体思路:先用并查集查找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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值