参考及图片来源:https://comzyh.com/blog/archives/148/
二分图:简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图。准确地说:把一个图的顶点划分为两个不相交集 U 和 V ,使得每一条边都分别连接U 、 V 中的顶点。如果存在这样的划分,则此图为一个二分图。二分图的一个等价定义是:不含有「含奇数条边的环」的图。
(即 如果题目中的 元素 能够 被 分成 两组不同的点集,就是可以用二分图表示的,每个组内的点互不相连,边只在两个点集间产生 )
如下图:
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。下图 是一个最大匹配,它包含 4 条匹配边。
完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。上图 是一个完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。
求二分图最大匹配可以用最大流(Maximal Flow) (更复杂) 或者匈牙利算法(Hungarian Algorithm)
下面介绍匈牙利算法:
http://www.nocow.cn/index.php/Translate:USACO/stall4
以此题为例:
算法最基本轮廓:
- 置边集M为空(初始化,谁和谁都没连着)
- 选择一个新的原点寻找增广路
- 重复(2)操作直到找不出增广路径为止(2,3步骤构成一个循环)
模拟步骤如上图所示(过于详细,大牛请无视):
- 初始化(清空)
- 从A所连接的点中找到一个未在本次循环中搜索过的点2,并将2标记为搜索过,因为2没有被连接过,匹配A2
- 结束上次,开始新的循环,将所有点标记为未搜索过
- 搜索B,找到一个未在本次循环中搜索过的点2,标记为搜索过
- 发现2被匹配过,从2的父亲A寻找增广路,递归搜索A{从A所连接的点中找到一个未在本次循环中搜索过的点5(1已经标记为绿色),将5标记为搜索过,因为5没有被匹配过,匹配A5}找到增广路(此处为增广路的关键)
- 结束上次,开始新的循环,将所有点标记为未搜索过
- 搜索C,找到一个未在本次循环中搜索过的点1,并将1标记为搜索过,发现1未被匹配过,匹配C1
- 结束上次,开始新的循环,将所有点标记为未搜索过
- 搜索D,找到一个未在本次循环中搜索过的点1,并将1标记为搜索过,发现1被匹配过,递归搜索1的源C寻找增广路
- {搜索C,找到一个未在本次循环中搜索过的点5,标记为搜索过,发现5被匹配,进一步返现没有其他可连接点,返回找不到增广路}返回第9步
- 搜索D,找到一个未在本次循环中搜索过的点2,发现2被匹配,递归搜索2的源B寻找增广路
- {搜索B,找到一个未在本次循环中搜索过的点3,并将3标记为搜索过,发现3未被匹配,匹配B3返回找到}既然B另寻新欢,匹配D2
- 结束上次,开始新的循环,将所有点标记为未搜索过,递归搜索D寻找增广路
- 搜索E,找到一个未在本次循环中搜索过的点2,并将2标记为搜索过,发现2被匹配过,递归搜索2的源D寻找增广路
- {搜索D,发现1,5均被匹配过,返回找不到增广路}
- E无其他可连接节点,放弃E,E后无后续节点,已经遍历A-E,结束算法
例题: 51NOD 2006 (看了code就明白)
https://www.51nod.com/onlineJudge/questionCode.html#problemId=2006¬iceId=293729
#include <bits/stdc++.h>
using namespace std;
const int AX = 200;
int g[AX][AX];
int linker[AX];
bool used[AX];
int m,n;
bool dfs( int u ){
int v;
for( v = m+1 ; v <= n ;v ++ ){
if( g[u][v] && !used[v] ){ //如果u 和 v 能够相连 并且 点未被访问
used[v] = true; //标记点
if( linker[v] == -1 || dfs(linker[v]) ){ //如果点没有连接边 或者 已连接 但回溯 到其相连的点发现那个点仍能链接别的点,就将这个点的边变换
linker[v] = u; //链接新边
return true; //返回真,匹配成功
}
}
}
return false;
}
int xyl(){
int res = 0;
int u;
memset(linker,-1,sizeof(linker)); //首先各边初始化为未连接
for( u = 1 ; u <= m ; u++ ){
memset(used,0,sizeof(used)); //每次都将点设置为为访问过
if( dfs(u) ) res++; //用DFS查找匹配
}
return res;
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0);
cin>>m>>n;
int x,y;
while( cin>>x>>y && x != -1 && y != -1 ){
g[x][y] = 1; //加入题目给出的边
}
int ans = xyl(); //调用匈牙利算法
if( ans == 0 ) printf("No Solution!\n");
else cout<<ans<<endl;
return 0;
}