• 无向图判环+判连通性


    理论链接

    例题
    \(HDU1272\) 小希的迷宫
    \(AcWing\) \(4290\). 小希的迷宫

    题目要求:判断是不是连通无环的图

    总结
    1、必须使用\(scanf\)!使用\(cin\)直接\(TLE\)!
    2、无向图判环,应尽量采用并查集(判断连通性)+公式法(判环)
    3、

    方法1:数学公式法判环+并查集判连通性

    判断一个图是否为连通图且没有环其实就一个条件,点数-1=边数,但是需要特判(0,0)
    此方法可以通过AcWing+HDU

    方法2:并查集

    • 并查集判环,不用真的建图
    • 因为不一定每个点都用上,所以需要记录本轮使用了哪些节点
    • 检查是否有环:
      判断输入边的两个点,如果它们已经在同一个家族内,说明有环
    • 无环情况下,还需要检查是不是连通图。
      这时,需要判断家族数量是不是\(1\)
      此方法可以通过AcWing+HDU

    方法3:\(DFS\)标记白灰黑

    • \(DFS\)判环,需要使用邻接表建图

    • 因为不一定每个点都用上,所以需要记录本轮使用了哪些节点

    • 检查是否有环:
      (1)枚举每个节点,找到一个使用过的节点进去开始深搜
      (2)需要单开一个状态数组\(vis\),来记录哪些节点深搜过了

    • 在无环情况下,还需要判断是不是连通
      枚举每个节点,如果出现过,但深搜没有搜索过,就意味着不连通
      此方法HDU可AC,AcWing会SE,原因可能是因为内存过大。

    并查集+公式

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 100010;
    
    bool st[N];
    bool flag;
    int edges, points;
    //最简并查集
    int p[N];
    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]); //路径压缩
        return p[x];
    }
    //合并集合
    bool join(int a, int b) {
        if (find(a) == find(b)) return false;
        p[find(a)] = find(b);
        return true;
    }
    
    //初始化并查集
    void init() {
        for (int i = 0; i < N; i++) p[i] = i;
        //初始化状态数组
        memset(st, false, sizeof(st));
        flag = true;
        edges = points = 0;
    }
    
    int main() {
        int a, b;
        //初始化
        init();
        while (~scanf("%d%d", &a, &b), ~a && ~b) {
            if (a && b) {
                edges++; //增加一条边
                if (!st[a]) {
                    st[a] = true;
                    points++;
                }
                if (!st[b]) {
                    st[b] = true;
                    points++;
                }
                if (!join(a, b)) flag = false;
            } else {
                //这个是应付AcWing的黑心数据 0 0 -1 -1而加的特判
                //如果连通,并且,无环
                flag && (edges == points - 1 || edges == 0) ? puts("Yes") : puts("No");
                init();
            }
        }
        return 0;
    }
    

    并查集+枚举

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 100500;
    
    int n, m;         // n个顶点,m条边
    bool flag = true; //默认是合格的,如果有环是false,不连通也是false
    //每组数据,是否符合要求:1、任意两点间道路唯一,2、需要是连通的
    bool st[N]; //节点是不是出现过
    
    //最简并查集
    int p[N];
    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]); //路径压缩
        return p[x];
    }
    //合并集合
    bool join(int a, int b) {
        if (find(a) == find(b)) return false;
        p[find(a)] = find(b);
        return true;
    }
    
    // 初始化各种状态数组和标识数据
    void init() {
        flag = true;
        //初始化并查集
        for (int i = 0; i < N; i++) p[i] = i;
        //清空状态数组
        memset(st, 0, sizeof st);
    }
    
    int main() {
        int a, b;
        //初始化
        init();
        while (~scanf("%d%d", &a, &b), ~a && ~b) {
            if (a && b) {               //一组数据输入进行中
                st[a] = st[b] = true;   //标识a,b两个点走过了
                if (!join(a, b))        //如果两个点在一个集合中,说明有重复路线出现~
                    flag = false;        //标记已经不合法
            } else {
                //单组数据输入结束
                //如果没有检查到环,才有资格检查是不是连通
                if (flag) {
                    int cnt = 0;
                    for (int i = 1; i < N; i++) {
                        if (st[i] && find(i) == i) cnt++; //判断根节点cnt数目
                        if (cnt > 1) {
                            flag = false;
                            break;
                        }
                    }
                }
                //检查通过
                flag ? puts("Yes") : puts("No");
    
                //清空准备下一次输入
                init();
            }
        }
        return 0;
    }
    

    dfs

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5 + 10, M = N << 1;
    /*
    7e5+10
    =700000*4/1024/1024=2.67MB
    这内存也不大啊,为啥会SE呢?不是说64MB的上限吗?
    被卡的好难受,这是逼我去用并查集+公式解题啊!
    */
    //邻接表
    int e[M], h[N], idx, ne[M];
    void add(int a, int b) {
        e[idx] = b, ne[idx] = h[a], h[a] = idx++;
    }
    
    bool st[N];  //标识某个节点号是不是使用过
    bool vis[N]; //是不是在dfs过程中搜索过
    int flag;    // true:满足两个条件 false:不满足两个条件 (1)是不是有环,要求无环 (2)是不是连通,要求连通
    
    // dfs无向图判环
    void dfs(int u, int fa) {               //从u节点开始搜索,它的父节点是fa,输入fa的意义在于不能走回头路,防止死循环
        vis[u] = true;                      //标识u已经搜索过了
        for (int i = h[u]; ~i; i = ne[i]) { //枚举u的每一条出边
            int j = e[i];
            if (j == fa) continue; //放过来源节点
            if (vis[j]) {          //如果j被搜索过,说明有环
                flag = false;      //标识有环
                return;
            }
            dfs(j, u); //继续搜索j节点,它的父节点是u
        }
    }
    //每组输入的清空与初始化
    void init() {
        memset(st, 0, sizeof(st));
        memset(vis, 0, sizeof(vis));
        memset(h, -1, sizeof h);
        idx = 0;
    }
    
    int main() {
        int a, b;
        init();
        while (scanf("%d%d", &a, &b) && ~a && ~b) {
            if (a && b) {                   //如果输入的a,b是两个非零数字
                st[a] = st[b] = true;       //标识a,b已使用
                add(a, b), add(b, a);       //添加两条无向边
            } else {                        //如果输入的是两个0,描述是一组数据输入结束
                flag = true;                //默认是无环+连通图
                // 1、检查是不是无环
                for (int i = 1; i < N; i++) //枚举每个节点
                    if (st[i]) {            //如果i节点使用过
                        //从找到的第一个使用过的节点开始进行dfs扫描
                        dfs(i, i);
                        break;
                    }
                // 2、检查连通性
                if (flag) { //扫描完的结果:1、找到了环,2、没有找到环
                    //下面将现次枚举每个使用过的点,查看它是不是在dfs过程中被扫描到了,如果存在使过未扫到的点,则说明不连通
                    for (int i = 1; i < N; i++)
                        if (st[i] && !vis[i]) { //如果存在出现过,但dfs没有扫描到的点,说明不连通
                            flag = false;
                            break;
                        }
                }
                flag ? puts("Yes") : puts("No"); //两个条件都满足,输出Yes,否则输出No
                init();                          //因为要输入下一组数据,需要清空状态数组
            }
        }
        return 0;
    }
    
  • 相关阅读:
    攻城狮在路上(肆)How tomcat works(一) 简单的web服务器
    攻城狮在路上(肆)How tomcat works(零) 前言说明
    font-face使用备忘
    subversion安装使用
    判断一个类到底是从哪个jar包中调用的工具类
    JavaScript实例
    Nginx(一)
    PHP面向对象(七)
    PHP面向对象(六)
    PHP面向对象(五)
  • 原文地址:https://www.cnblogs.com/littlehb/p/16277763.html
Copyright © 2020-2023  润新知