定义
如果一个图((E,V))的顶点集(E)能够被能够被分成两个不相交的集合(X,Y),且每一条边都恰连接(X,Y)中的各一个顶点,那么这个图就是一个二分图。
容易得知,它就是不含有奇数环的图(这个等价定义有时候很重要)。
一个匹配是一个边的集合,其中任意两条边都没有公共顶点。顾名思义可以得到一个图的最大匹配的定义。特别地,如果一个图的某个匹配中,所有顶点都是匹配点,那么它是一个完美匹配。
由完美匹配和最大匹配这两个定义我们可以得到两类问题:
- 有没有可能使得所有顶点都被匹配?
- 一个图中最多有多少个顶点参与了匹配?
求解最大匹配:匈牙利算法
对于一个正在求匹配的图,我们把依次经过非匹配边、匹配边、非匹配边、……形成的路径叫交替路;而如果一个未匹配点走交替路到了另外一个未匹配点,那么这条交替路成为增广路。
仔细思考一下,就会发现增广路的一个特点:非匹配边比匹配边多一条。这有什么意义呢?当我们把增广路中匹配边、非匹配边的性质交换后,匹配的性质不变,但是匹配边多了一条——我们改进了匹配。
因此,我们可以通过不停的找增广路来增加匹配中的匹配边和匹配点。然后,根据增广路定理,一个图找不到增广路时,就是它达到最大匹配的时候。
下面简单的说一下匈牙利算法:
- 从二分节点后的左边第1个顶点开始,挑选未匹配点进行搜索,寻找增广路。
a. 如果经过一个未匹配点,说明寻找成功。更新路径信息,匹配边数+1,停止搜索。
b. 如果一直没有找到增广路,则不再从这个点开始搜索。事实上,此时搜索后会形成一棵匈牙利树。我们可以永久性地把它从图中删去,而不影响结果。 - 由于找到增广路之后需要沿着路径更新匹配,所以我们需要一个结构来记录路径上的点。DFS 版本通过函数调用隐式地使用一个栈,而 BFS 版本使用
pre
数组。
上面提到了匈牙利树。那么这个是什么?匈牙利树他是这样的性质,从根节点到叶节点的路径均是交替路,且匈牙利树的叶节点都是匹配点。
例题:HDU-2063 过山车
经典板子题。
#include <bits/stdc++.h>
#define MP make_pair
#define PB push_back
#define fi first
#define se second
#define ZERO(x) memset((x), 0, sizeof(x))
#define ALL(x) (x).begin(),(x).end()
#define rep(i, a, b) for (repType i = (a); i <= (b); ++i)
#define per(i, a, b) for (repType i = (a); i >= (b); --i)
#define QUICKIO
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
using namespace std;
typedef long long ll;
typedef int repType;
const int MAXN=505;
int k,m,n;
bool mat[MAXN][MAXN],used[MAXN];
int linker[MAXN];
int dfs(int boy)
{
rep(g,1,n) // girl
{
if(mat[boy][g] && !used[g])
{
used[g]=true;
if(linker[g]==-1 || dfs(linker[g]))
{
linker[g]=boy;
return true;
}
}
}
return false;
}
int hungary()
{
int ans=0;
memset(linker,-1,sizeof(linker));
rep(b,1,m) // boy
{
ZERO(used);
if(dfs(b)) ans++;
}
return ans;
}
int main()
{
while(cin>>k)
{
if(!k) break;
cin>>m>>n;
ZERO(mat);
rep(i,1,k)
{
int a,b; cin>>a>>b;
mat[a][b]=true;
}
cout<<hungary()<<endl;
}
return 0;
}
其他的题型
简单改一下:HYSBZ - 1191 超级英雄Hero
最小路径覆盖
二分图最大独立集
参考网址与资源
https://www.renfei.org/blog/bipartite-matching.html
https://www.cnblogs.com/YiXiaoZhou/p/5875040.html
https://www.cnblogs.com/kuangbin/archive/2012/08/26/2657446.html