定义
如果一张无向图的 \(N\) 个节点( \(N \geq 2\) ),可以分成 \(U\) , \(V\) 两个非空集合,其中 \(U \cap V = \Phi\) ,并且在同一集合内的点之间都没有边相连,那么称这张无向图为一张二分图。
\(U\) , \(V\) 分别为二分图的左部和右部。
顶点集 \(U\) , \(V\) 被称为是图的两个部分。
等价的 , 二分图 可以被定义成 图中所有的环都有偶数个顶点。
二分图判定
证明
如果某个图是二分图,那么它至少有两个结点,且所有回路的长度均为偶数,任何无回路的图均是二分图。
一旦添加一条边后图中出现了回路,且长度一定为奇数,则该图就不再是二分图。
思想
根据上述定理,可以用染色法进行二分图判定。
用黑白两种颜色标记图中的节点,当一个节点被标记后,它的所有相邻节点应该被标记成与它相反的颜色。每个点只标记一次。
若标记过程中产生冲突,则说明存在奇环。
时间复杂度为 \(O ( N + M )\) 。
实现
/*
By 《算法竞赛进阶指南》
*/
void dfs(int x, int col)
{
赋值 v[x] ← col;
对于与 x 相连的每条无向边(x, y) if (v[y] = 0)
dfs(y, 3 - col) else if (v[y] = col)
{
不是二分图;
return;
}
}
主函数中
{
for (i = 1 → N)
if (v[i] = 0)
dfs(i, 1);
判定无向图是二分图;
}
二分图匹配
云:
学长云:
上图中的选择方案即为原图的一种匹配。
二分图最大匹配
云:
学长又云:
匈牙利算法(增广路算法)
核心
即寻找增广路径 , 它是一种用 增广路径 求 二分图最大匹配的算法。
设 \(S=\Phi\) ,即所有边都是非匹配边。
寻找增广路 \(path\) ,把路径上所有边的匹配状态取反,得到一个更大的匹配 \(S'\) 。
重复第 \(2\) 步,知道图中不存在增广路。
观摩学长给出的一个样例
对 \(Yugari\) 进行匹配 :
其直接连接点 \(Reimu\) 未被匹配 , 则将 \(Yugari\) 与 \(Reimu\) 进行匹配
对 \(Marisa\) 进行匹配 :
其直接连接点 \(Patchouli\) 未被匹配 , 则将 \(Marisa\) 与 \(Patchouli\) 进行匹配
对 \(Suika\) 进行匹配 :
其直接连接点 \(Reimu\) 被匹配 , 检查 \(Reimu\) 的匹配点 \(Yugari\) 能否寻找到其他匹配点
\(Yugari\) 可与 \(Yuyuko\) 进行匹配 , 则将 \(Yugari\) 与 \(Yuyuko\) 进行匹配
由于\(Yugari\) 匹配对象改变 , \(Reimu\) 未被匹配 , 则将 \(Suika\) 与 \(Reimu\) 进行匹配
对 \(Aya\) 进行匹配 :
其直接连接点 \(Reimu\) 被匹配 , 检查 \(Reimu\) 的匹配点 \(Suika\) 能否寻找到其他匹配点
\(Suika\) 无其他匹配点 , 不可将 \(Suika\) 与其他结点进行匹配
由于 \(Suika\) 匹配对象不可改变 , \(Reimu\) 被匹配 , 则 \(Aya\) 无匹配点
则此二分图的一种最大匹配为 :
例题
P3386 【模板】二分图匹配
[P3386 【模板】二分图匹配](P3386 【模板】二分图匹配)
- 如题,模板题是了。
/*
Name: P3386 【模板】二分图匹配
Solution: 二分图匹配
By Frather_
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
/*=========================================快读*/
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x * f;
}
/*=====================================定义变量*/
int n, m, t;
const int _ = 10010;
struct edge
{
int from;
int to;
int nxt;
} e[_];
int cnt, head[_];
bool vis[_];
int mc[_];
int ans;
/*===================================自定义函数*/
void add(int from, int to)
{
e[++cnt].from = from;
e[cnt].to = to;
e[cnt].nxt = head[from];
head[from] = cnt;
}
bool check(int u_)
{
for (int i = head[u_]; i; i = e[i].nxt)
if (!vis[e[i].to])
{
vis[e[i].to] = true;
if (!mc[e[i].to] || check(mc[e[i].to]))
{
mc[e[i].to] = u_;
return true;
}
}
return false;
}
/*=======================================主函数*/
int main()
{
n = read();
m = read();
t = read();
for (int i = 1; i <= t; i++)
{
int u = read();
int v = read();
add(u, v);
}
for (int i = 1; i <= n; i++)
{
memset(vis, false, sizeof(vis));
if (check(i))
ans++;
}
printf("%d\n", ans);
return 0;
}
P2756 飞行员配对方案问题
- 匹配完成后输出各点匹配对象非 0 的即可。
/*
Name: P2756 飞行员配对方案问题
Solution: 二分图匹配
By Frather_
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
/*=========================================快读*/
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x * f;
}
/*=====================================定义变量*/
int n, m, t;
const int _ = 10010;
struct edge
{
int from;
int to;
int nxt;
} e[_];
int cnt, head[_];
bool vis[_];
int mc[_];
int ans;
/*===================================自定义函数*/
void add(int from, int to)
{
e[++cnt].from = from;
e[cnt].to = to;
e[cnt].nxt = head[from];
head[from] = cnt;
}
bool check(int u_)
{
for (int i = head[u_]; i; i = e[i].nxt)
if (!vis[e[i].to])
{
vis[e[i].to] = true;
if (!mc[e[i].to] || check(mc[e[i].to]))
{
mc[e[i].to] = u_;
return true;
}
}
return false;
}
/*=======================================主函数*/
int main()
{
n = read();
m = read();
while (1)
{
int u = read();
int v = read();
if (u == -1 || v == -1)
break;
add(u, v);
}
for (int i = 1; i <= n; i++)
{
memset(vis, false, sizeof(vis));
if (check(i))
ans++;
}
printf("%d\n", ans);
for (int i = n + 1; i <= m; i++)
if (mc[i])
printf("%d %d\n", mc[i], i);
return 0;
}
写在后面
本文图片均来自 @Lucky Block。
----至已逝去的不是学长大大(((不是
鸣谢:
《算法竞赛进阶指南》
@Lucky Block
OI-wiki
百度百科
Luogu