基环图 && 求联通块个数
------------------------------基环树------------------------------
前置知识
生成树:什么是生成树,生成树(生成森林)详解 (biancheng.net)
基环树:具有N个点N条边的连通图。
连通图必然存在生成树
基环树
1.概念: 简单来说,基环树就是一个环上挂着一堆树。
2.两个性质(充要条件)
(1)连通图
(2)边数m=点数n
证明:
(1)必要性:一个基环树肯定是一个连通图(树是连通图,环是连通图)。我们断掉基环树环上的任意一条边,基环树就会成为一棵普通的树,树有m=n-1。
(2)充要性:由于性质(1)我们可知该图是一个连通图,连通图一定可以求一棵生成树,生成树有m=n-1,说明此时我们还有一条边没有用。因为这条没用的边无论加在生成树的任何地方,都只能构成一个环,所以该连通图一定是一基环树。
(基环树)
思路:由性质判断该图是不是基环树,主要判断是不是一个连通图
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;
}
求连通块个数
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;
}