c
casperwu
V1
2022/07/20阅读:8主题:橙心
二分图
二分图与匈牙利算法
1. 定义
设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边所关联的两个顶点i和j分别属于这两个不同的顶点集,则称图G为一个二分图。
2. 二分图的判定
给定一个图,可以使用染色法来判断这个图是否是一个二分图。
// 使用dfs方法染色
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100005, M = 200005;
int h[N], e[M], ne[M], idx;
int color[N];
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
// 返回true表示是二分图, false 表示不是二分图
bool dfs(int u, int c) {
color[u] = c;
for(int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if(!color[j]) {
// 给另一端染成 2 号颜色(3 - 1 = 2)
if(!dfs(j, 3 - c)) return false;
} else if(color[j] == c) return false; // 一条边的两端颜色相同, 表示染色失败, 不是二分图
}
return true;
}
int main() {
int n, m;
memset(h, -1, sizeof(h));
cin >> n >> m;
int a, b;
while(m--) {
cin >> a >> b;
add(a, b), add(b, a);
}
bool flag = true;
for(int i = 1; i <= n; i++) {
if(!color[i]) {
if(!dfs(i, 1)) { // 给i点染成1号颜色, 递归染色, 染色失败, 直接跳出循环
flag = false;
break;
}
}
}
if(flag) puts("Yes");
else puts("No");
return 0;
}
3. 二分图的用途
可用于处理最大匹配的问题。常用的算法是:匈牙利算法。
3.1 题目

3.2 题意理解
如果两个格子之间能相互攻击,则在这两个格子之间建立一条边; 原题就等价于 选最多的点使得两两之间没有边。
能使用匈牙利算法来解决的前提条件是:这个图是一个二分图。 证明此题中的骑士摆放是一个二分图:
-
给棋盘染色, 横纵坐标和为偶数的染成黑色, 横纵坐标和奇数染成白色. 此时可以发现当棋子位于黑色格子时,可以跳的格子都是白色的.因此所有的边都在两个集合之间。
-
假设当前格子的坐标为(x, y),则当跳了一步之后,nx + ny 的奇偶性与 x + y 的奇偶性是相反的。因为在跳的过程中坐标值的变换是一个奇数, 因此一个数加减一个奇数,它的奇偶性一定会发生变换。因此是一个二分图。
3.3 示例代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
// g[][] 记录某个点能否可以放棋子
bool g[N][N];
bool st[N][N];
PII match[N][N]; // 记录某个点的匹配点
int n, m, k;
int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
bool find(int x, int y) {
for(int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
if(g[nx][ny]) continue;
if(st[nx][ny]) continue;
st[nx][ny] = true;
PII t = match[nx][ny];
if(t.first == 0 || find(t.first, t.second)) {
match[nx][ny] = {x, y};
return true;
}
}
return false;
}
int main() {
cin >> n >> m >> k;
for(int i = 0; i < k; i++) {
int a, b; cin >> a >> b;
g[a][b] = true;
}
int ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if((i + j) % 2 && !g[i][j]) {
memset(st, 0, sizeof(st));
if(find(i, j)) ans++;
}
}
}
cout << n * m - k - ans;
return 0;
}
作者介绍
c
casperwu
V1