基环图 && 求联通块个数

          ------------------------------基环树------------------------------

前置知识

生成树:什么是生成树,生成树(生成森林)详解 (biancheng.net)​​​​​​ 

基环树:具有N个点N条边连通图

连通图必然存在生成树

基环树 

1.概念: 简单来说,基环树就是一个环上挂着一堆树。

2.两个性质(充要条件)

(1)连通图

(2)边数m=点数n

证明:

(1)必要性:一个基环树肯定是一个连通图(树是连通图,环是连通图)。我们断掉基环树环上的任意一条边,基环树就会成为一棵普通的树,树有m=n-1。

(2)充要性:由于性质(1)我们可知该图是一个连通图,连通图一定可以求一棵生成树,生成树有m=n-1,说明此时我们还有一条边没有用。因为这条没用的边无论加在生成树的任何地方,都只能构成一个环,所以该连通图一定是一基环树。

                                                                 (基环树)

应用:4216. 图中的环 - AcWing题库

思路:由性质判断该图是不是基环树,主要判断是不是一个连通图

1.并查集(连通块个数为1就是一个连通图)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1100;

int n, m, pre[N];

int find(int x)
{
    if(x == pre[x]) return pre[x];
    return pre[x] = find(pre[x]);
}

int main()
{
    cin >> n >> m;
    if(n != m)  puts("NO");
    else
    {
        for(int i = 0; i < N; i ++ )    pre[i] = i;
        int cnt = n;
        while (m -- )
        {
            int a, b;

            cin >> a >> b;
            if(find(a) != find(b))
            {
                pre[find(b)] = find(a);
                cnt -- ;
            }
        }
        if(cnt == 1)    puts("YES");
        else    puts("NO");
    }
    
    return 0;
}

2.DFS

#include<bits/stdc++.h>

using namespace std;

int n, m, a, b;
bitset<110> st[110], v;//st:存边,v:dfs是否遍历过

void dfs(int x)
{
    v[x] = 1;
    for(int i = 1; i <= n; i++)
        if(st[x][i] && !v[i]) dfs(i);// x -> i 有边,且 i 未被遍历过
}
int main()
{
    cin >> n >> m;
    for(int i = 0; i < m; i++)
        if(cin >> a >> b) st[a][b] = 1, st[b][a] = 1;
    dfs(1);// 深度遍历随便一个点
    if(m == n && v.count() == n) cout << "YES\n";//如果可以到达所有点,说明是连通图
    else cout << "NO\n";
    return 0;
}


求连通块个数

例题:1562 -- Oil Deposits (poj.org)

1.DFS(推荐)

对于每一个@点,从该点出发,深度搜索所有与它相邻的@点
每访问一个@点,连通分量就加一
在访问之前先判断该点有没有访问过(设置st数组),这样就可以在访问之前检查它是否已经访问过,从而避免同一格子访问多次

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int n, m;
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};
char g[N][N], st[N][N];

bool check(int x, int y)
{
    if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == '@' && !st[x][y]) 
        return true;
    return false;
}

void dfs(int x, int y)
{
    if(!check(x, y))    return ;
    
    st[x][y] = true;
    for(int i = 0; i < 8; i ++ )
    {
        int xx = x + dx[i], yy = y + dy[i];
        if(check(xx, yy))   dfs(xx, yy);
    }
}

int main()
{
    while(cin >> n >> m, m)
    {
        for(int i = 0; i < n; i ++ )    cin >> g[i];
        
        memset(st, false, sizeof st);   //不要忘了
        int res  = 0;
        for(int i = 0; i < n; i ++ )
            for(int j = 0; j < m; j ++ )
                if(g[i][j] == '@' && !st[i][j])
                {
                    dfs(i, j);
                    res ++;
                }
                
        cout << res << endl;
    }
    
    return 0;
}

2.BFS(不推荐)

对于每一个@点,从该点出发,广度搜索所有与它相邻的@点
每访问一个@点,连通分量就加一
在访问之前先判断该点有没有访问过(设置st数组),这样就可以在访问之前检查它是否已经访问过,从而避免同一格子访问多次

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 110;

int n, m;
char g[N][N];
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};
bool st[N][N];

bool check(int x, int y)
{
    if(x >= 0 && y >= 0 && x < n && y < m && g[x][y]== '@' && !st[x][y])
        return true;
    return false;
}

void bfs(int x, int y)
{
    queue<PII> q;
    
    q.push({x, y});
    st[x][y] = true;
    
    while(q.size())
    {
        PII t = q.front();   
        q.pop();
        
        for(int i = 0; i < 8; i ++ )
        {
            int xx = t.x + dx[i], yy = t.y + dy[i];
            if(check(xx, yy))    
            {
                q.push({xx, yy});
                st[xx][yy] = true;
            }
        }
    }
}

int main()
{
    while(cin >> n >> m, m)
    {
        for(int i = 0; i < n; i ++ )    cin >> g[i];
        
        memset(st, false, sizeof st);
        
        int res = 0;
        for(int i = 0; i < n; i ++ )
            for(int j = 0; j < m; j ++ )
                if(check(i, j))
                {
                    bfs(i, j);
                    res ++ ;
                }
            
        cout << res << endl;
    }
    
    return 0;
}

3.并查集(推荐)

将n行m列的二维数组上的点(x,y)映射到一维数组上的点(c)公式:c=x*m+y

(1)集合的思想:开始时将每一个@看做一个集合,后面每合并两个相邻的@,集合数量就减一,最后剩余的集合数量就是连通块的数量。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 100010;

char g[N][N];
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};
int n, m, pre[M];

int find(int x)
{
    if(x == pre[x]) return x;
    return pre[x] = find(pre[x]);
}

bool merge(int x, int y)
{
    if(find(x) == find(y))  return false;
    
    pre[find(x)] = find(y);
    return true;
}

bool check(int x, int y)
{
    if(x >= 0 && y >= 0 && x < n && y < m && g[x][y]== '@')
        return true;
    return false;
}

void init()
{
    for(int i = 0; i < M; i ++ )    pre[i] = i;
}

int main()
{
    while(cin >> n >>m, m)
    {
        init();
        for(int i = 0; i < n; i ++ )    cin >> g[i];
        
        int res = 0;
        for(int i = 0; i < n; i ++ )
        {
            for(int j = 0; j < m; j ++ )
            {
                if(g[i][j] == '@')
                {
                    res ++ ;
                    for(int k = 0; k < 8; k ++ )
                    {
                        int x = i + dx[k], y = j + dy[k];
                        if(check(x, y)) res -= merge(i * m + j, x * m + y);
                    }
                }
            }
        }
        
        cout << res << endl;
    }
    
    return 0;
}

(2)父节点个数:合并完相邻的节点之后,父节点的个数就是连通块的个数

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 100010;

char g[N][N];
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};
int n, m, pre[M];

int find(int x)
{
    if(x == pre[x]) return x;
    return pre[x] = find(pre[x]);
}

bool merge(int x, int y)
{
    if(find(x) == find(y))  return false;
    
    pre[find(x)] = find(y);
    return true;
}

bool check(int x, int y)
{
    if(x >= 0 && y >= 0 && x < n && y < m && g[x][y]== '@')
        return true;
    return false;
}

void init()
{
    for(int i = 0; i < M; i ++ )    pre[i] = i;
}

int main()
{
    while(cin >> n >>m, m)
    {
        init();
        for(int i = 0; i < n; i ++ )    cin >> g[i];

        for(int i = 0; i < n; i ++ )
        {
            for(int j = 0; j < m; j ++ )
            {
                if(g[i][j] == '@')
                {
                    for(int k = 0; k < 8; k ++ )
                    {
                        int x = i + dx[k], y = j + dy[k];
                        if(check(x, y)) merge(i * m + j, x * m + y);
                    }
                }
            }
        }
        
        int res = 0;
        for(int i = 0; i < n; i ++)
            for(int j = 0; j < m; j ++ )
                if(find(i * m + j) == (i * m + j) && g[i][j] == '@')    res ++;
        
        cout << res << endl;
    }
    
    return 0;
}

posted @ 2022-05-05 08:41  光風霽月  阅读(78)  评论(0编辑  收藏  举报