[蓝桥杯][历届试题 PREV-49]发现环(Java)(并查集+DFS)

历届试题 发现环  

时间限制:1.0s   内存限制:256.0MB

    

问题描述

  小明的实验室有N台电脑,编号1~N。原本这N台电脑之间有N-1条数据链接相连,恰好构成一个树形网络。在树形网络上,任意两台电脑之间有唯一的路径相连。


  不过在最近一次维护网络时,管理员误操作使得某两台电脑之间增加了一条数据链接,于是网络中出现了环路。环路上的电脑由于两两之间不再是只有一条路径,使得这些电脑上的数据传输出现了BUG。


  为了恢复正常传输。小明需要找到所有在环路上的电脑,你能帮助他吗?

输入格式

  第一行包含一个整数N。
  以下N行每行两个整数a和b,表示a和b之间有一条数据链接相连。


  对于30%的数据,1 <= N <= 1000
  对于100%的数据, 1 <= N <= 100000, 1 <= a, b <= N


  输入保证合法。

输出格式

  按从小到大的顺序输出在环路上的电脑的编号,中间由一个空格分隔。

样例输入

5
1 2
3 1
2 4
2 5
5 3

样例输出

1 2 3 5


反思错误:

①想到了并查集,但是没有细想,忽略了成环的条件是当前两点已经存在于同一个集合之中了。

②想了想最费劲的方法,循环将每个出入度为一的点及其连接的线删除,直到剩下所有出入度为二的点。(特别耗时,循环太多)

③最操蛋的是,思路没问题了,C能过,Java超时。(至今无法解决,什么时候解决,什么时候再来修改吧【PS:现在优化好了,在原来的基础上加上了快读、快输,又优化了dfs 、 点点关系结构 和 并查集存储结构】

解题思路:

并查集就不细说了,直接看这题,

在每次循环输入中,我们对两个点find_root(查看是否在同一个集合之中),不在同一个集合中则放到同一集合下,如果在同一个集合中,那么这两点一定在环上(因为同一集合代表两点间有一条路了,这次循环输入又出来一条路了)。

那么我们只需要用dfs,从一个点到另一个点,不重边经过的所有节点就是构成环的所有点。排序,输出。

Java代码:

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) {
        //此处实例化快读
        IN cin = new IN(System.in);

        So so = new So();

        so.sol(cin);
    }

    //解决方案 具体实现部分
    static class So{
        int[] f;//并查集,关系存储
        void sol(IN cin){
            int N = cin.nextInt();
            f = new int[N+1];
            List<Integer>[] tree = new List[N+1];//点点间的关系,因为不知道是几叉树的树状结构,所以用链表好用
            //实例化
            for(int i=1;i<=N;++i) {
                // ArraysList还是数组结构,LinkedList是链表。时隔多年又发现了以前错误的理解
                tree[i]=new ArrayList<>();
            }
            int rings = 0,ringe = 0;//rings :环开始的值,ringe:环结束的值
            for(int i=1;i<=N;++i) {
                int x = cin.nextInt(), y = cin.nextInt();

                if(rings==0) {
                    if(union(x,y)) {
                        rings = x;
                        ringe = y;
                    }

                    tree[x].add(y);
                    tree[y].add(x);

                }
            }

            int[] res = new int[N+1];
            boolean[] vis = new boolean[N+1];


            int top = dfs(tree,rings,ringe,res,0,vis);


            Arrays.sort(res,0,top);

            for(int i=0;i<top;++i) {
                cout.print(res[i]+" ");
            }
            cout.flush();
        }
        //并查集搜索根(压缩深度)
        int find(int x) {
            return f[x] == 0 ? x : (f[x] = find(f[x]));//(f[x]=find(f[x]))在每次查询中,递归将底层都指向根
        }
        //两点合并并查集,若在一个并查集中返回true
        boolean union(int x,int y) {
            int a = find(x), b = find(y);
            if(a==b) return true;
            f[a] = b;
            return false;
        }
        //DFS,访问过的点用vis标识,避免重复扫描
        int dfs(List<Integer>[] tree,int cur,int ringe,int[] res,int top,boolean[] vis) {
            if(top>0&&cur==ringe) {res[top]=cur;return top+1;}
            res[top] = cur;
            vis[cur]=true;
            for(int next:tree[cur]) {
                if(vis[next])continue;
                int h=dfs(tree,next,ringe,res,top+1,vis);
                if(h!=0)return h;
            }
            return 0;
        }

    }

    // 快写
    static PrintWriter cout = new PrintWriter(System.out);
    //下边这个类是 快读
    static class IN{
        private BufferedReader reader;
        private StringTokenizer tokenizer;

        public IN(InputStream is){
            reader = new BufferedReader(new InputStreamReader(is),32768);
            tokenizer = null;
        }
        public String next() {
            while(tokenizer==null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                }catch(IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }
    }

}

C++代码(一百分,思路和Java一样):

#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 100000+5;
int par[maxn], vis[maxn], ret[maxn];
vector<int> edge[maxn];
int n, s, f;

int findRoot(int x) {
    return par[x] == x ? x : par[x] = findRoot(par[x]);
}

void dfs(int u, int ind) {
    ret[ind] = u;
    if(u == f) {
        sort(ret, ret + ind + 1);
        for(int i = 0; i <= ind; i++) {
            printf("%d%c", ret[i], i==ind?'\n':' ');
        }
        return;
    }
    vis[u] = 1;
    for(int i = 0; i < edge[u].size(); i++) {
        int v = edge[u][i];
        if(!vis[v]) dfs(v, ind+1);
    }
    vis[u] = 0;
}

int main() {
    while(scanf("%d", &n) == 1) {
        int u, v;
        for(int i = 1; i <= n; i++) par[i] = i;
        for(int i = 0; i < n; i++) {
            scanf("%d%d", &u, &v);
            int ru = findRoot(u), rv = findRoot(v);
            if(ru == rv) s = u, f = v;
            else {
                par[ru] = rv;
                edge[u].push_back(v);
                edge[v].push_back(u);
            }
        }
        memset(vis, 0, sizeof(vis));
        dfs(s, 0);
    }
    return 0;
}

结果截图:

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值