二分图及其最大匹配
概念
二分图定义:二分图,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。如下图就是一个二分图:
注意:二分图不一定是连通图。
判定定理:一个图是二分图当且仅当没有长度为奇数的圈。
染色法判定二分图
从上面的判定定理可知, 我们可以通过染色法判断有没有奇圈。模板题点我,代码如下:
//DFS求解
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct edge{
int nex, to;
}Edge[maxn<<1];
int n, m, head[maxn], co[maxn], tot;
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool dfs(int u, int x){
co[u] = x;
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(co[v] == 0){
if(!dfs(v, 3 - co[u])) return 0;
}
else if(co[v] == co[u]) return 0;
else if(co[v] + co[u] == 3) continue;
}
return 1;
}
int main()
{
scanf("%d %d", &n, &m);
memset(head, -1, sizeof(head));
for(int i = 1; i <= m; ++i){
int a, b;
scanf("%d %d", &a, &b);
add(a, b); add(b, a);
}
int ok = 1;
for(int i = 1; i <= n; ++i){
if(co[i]) continue;
if(dfs(i, 1)) continue;
ok = 0;
break;
}
ok ? puts("Yes") : puts("No");
system("pause");
}
//BFS求解
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
queue<int> q;
struct edge{
int nex, to;
}Edge[maxn<<1];
int n, m, head[maxn], co[maxn], tot;
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool bfs(int x){
co[x] = 1;
q.push(x);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(co[v] == co[u]) return 0;
else if(co[v] == 0){
co[v] = 3 - co[u];
q.push(v);
}
else if(co[u] + co[v] == 3) continue;
}
}
return 1;
}
int main()
{
scanf("%d %d", &n, &m);
memset(head, -1, sizeof(head));
for(int i = 1; i <= m; ++i){
int a, b;
scanf("%d %d", &a, &b);
add(a, b); add(b, a);
}
int ok = 1;
for(int i = 1; i <= n; ++i){
if(co[i]) continue;
if(bfs(i)) continue;
ok = 0;
break;
}
ok ? puts("Yes") : puts("No");
system("pause");
}
二分图最大匹配
基本概念:
匹配:“任意两条边都没有公共端点”的边的集合称为匹配。
最大匹配:包含边的个数最多的一组匹配。
增广路:一个连接两个非匹配点的路径path, 使得非匹配边和匹配边在path上交替出现(第一条边和最后一条边都是未匹配边),称path是一个增广路。
求解二分图的最大匹配问题可用匈牙利算法(增广路算法)。下面给出模板代码:
模板题
【题意】:求解最大匹配的边数。时间复杂度为\(O(nm)\)。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
struct edge{
int nex, to;
}Edge[maxn * maxn];
int n, m, e;
int head[maxn], tot;
int match[maxn], vis[maxn], ans;
//vis表示每次搜索有没有被访问到, match表示点是不是匹配点
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool dfs(int u)
{
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v])){
match[v] = u;
return 1;
}
}
return 0;
}
int main()
{
scanf("%d %d %d", &n, &m, &e);
memset(head, -1, sizeof(head));
for(int i = 1; i <=e; ++i){
int u, v;
scanf("%d %d", &u, &v);
if(u > n || v > m) continue;
add(u, v);
}
for(int i = 1; i <= n; ++i){
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n", ans);
system("pause");
}
在将一个问题转化为二分图匹配模型时, 要注意两个要素:
0要素:节点能分成两个独立的集合, 每个集合内部有0条边
1要素:每个节点只能与1条匹配边相连
棋盘覆盖
【描述】:有n * n的棋盘, 输入一些坐标, 表示这些坐标中不能有1 * 2 的方块覆盖它。问这个棋盘上最多能放多少个1 * 2 的棋盘。
【思路】:由于是1*2的方块, 即一个方块连接另一个相邻的方块,这里可以看做是一个二分图模型。由于坐标和全为奇数的集合中任意两点不可能被同一个方块覆盖。所以我们可以将所有坐标和为奇数的点的集合去匹配坐标和为偶数的集合,求一下最大匹配即可。时间复杂度\(O(n^2m^2)\)。
0要素:坐标和为奇数的点和坐标和为偶数的点两个集合,且集合中没有边。
1要素:相邻坐标的坐标和奇偶性不同, 且只能连接一条边。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e4 + 10;
const int inf = 0x3f3f3f3f;
int gcd(int a,int b) { return b == 0 ? a : gcd(b, a % b); }
struct edge{
int nex, to;
}Edge[maxn<<2];
int n, t, m[110][110];
int head[maxn], tot;
int vis[maxn], match[maxn];
int ans;
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
inline int id(int x, int y) { return y + (x - 1) * n; }
bool dfs(int u)
{
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v])){
match[v] = u;
return 1;
}
}
return 0;
}
int main()
{
scanf("%d %d", &n, &t);
memset(head, -1, sizeof(head));
while(t--){
int a, b;
scanf("%d %d", &a, &b);
m[a][b] = 1;
}
//奇数点连偶数点
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
if((i + j) % 2 == 0) continue;
if(m[i][j]) continue;
if(i > 1 && !m[i-1][j]) add(id(i, j), id(i-1, j));
if(i < n && !m[i+1][j]) add(id(i, j), id(i+1, j));
if(j > 1 && !m[i][j-1]) add(id(i, j), id(i, j-1));
if(j < n && !m[i][j+1]) add(id(i, j), id(i, j+1));
}
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
if((i + j) % 2 == 0) continue;
for(int k = 1; k <= n*n; ++k) vis[k] = 0;
if(dfs(id(i, j))) ans++;
}
}
printf("%d\n", ans);
system("pause");
}
车的放置
【描述】:一个n * m的棋盘, 有的地方不能放车,问棋盘上最多能放多少个车,使得不相互攻击。
【思路】:0要素:1车不可能同时属于两行或两列。
1要素:一行与一列只能通过一个车匹配。
所以可以将所有的行当成1个集合, 所有的列当成一个集合,求一下最大匹配就好了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 4e4 + 10;
const int inf = 0x3f3f3f3f;
int gcd(int a,int b) { return b == 0 ? a : gcd(b, a % b); }
struct edge{
int nex, to;
}Edge[maxn];
int head[maxn], tot;
int n, m, t, arr[210][210];
int vis[maxn], match[maxn];
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool dfs(int u){
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v])){
match[v] = u;
return 1;
}
}
return 0;
}
int main()
{
scanf("%d %d %d", &n, &m, &t);
memset(head, -1, sizeof(head));
while(t--){
int u, v;
scanf("%d %d", &u, &v);
arr[u][v] = 1;
}
//行匹配列
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(arr[i][j]) continue;
add(i, j);
}
}
int ans = 0;
for(int i = 1; i <= n; ++i){
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n", ans);
system("pause");
}