参考资料:
算法简介
匈牙利算法主要用求二分图的最大匹配和最小覆盖
最大匹配问题
问题描述
一种形象的表述:把二分图中的一部分假设为男,另一部分为女,男生和女生之间有暧昧关系,求最大匹配就是求最多能促成多少对情侣。
算法思想
先到先得,能让则让。
代码
// 邻接矩阵存边
bool mat[maxn][maxn];
// vis[i]表示:i号女生是否已经配对
bool vis[maxn];
// p[i]表示:与i号女生配对的男生编号
int p[maxn];
bool match(int i){
for(int j=1;j<=n;j++){
// 该女生未配对,且这对男女之间有暧昧关系
if(!vis[j] && mat[i][j]){
vis[j] = 1;
/* match(p[j])表示让之前与i号女生配对的男生
换匹配对象 */
if(p[j]==0 || match(p[j])){
p[j] = i;
return true;
}
}
}
return false;
}
int Hungarian(){
int res = 0;
for(int i=1;i<=n;i++){
memset(vis, 0, sizeof(vis));
// 统计匹配成功的男生数
if(match(i)) res++;
}
return res;
}
例1 P1129 [ZJOI2007] 矩阵游戏
题目大意
给定一个由 0
和 1
构成的方阵,可以交换行和列,问能否通过交换操作使得方阵的主对角线上全为 1
。
思路
将矩阵转化为二分图,一部分代表各行,另一部分代表各列,如果方阵有 mat[i][j] == 1
,则在行 i
和列 j
之间建立一条边。对这个二分图求最大匹配,如果匹配数恰好等于方阵的行列数 n
,则输出 Yes
,否则输出 No
。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 205;
int T, n;
bool mat[maxn][maxn];
bool vis[maxn];
int p[maxn];
bool match(int i){
for(int j=1;j<=n;j++){
if(!vis[j] && mat[i][j]){
vis[j] = 1;
if(p[j]==0 || match(p[j])){
p[j] = i;
return true;
}
}
}
return false;
}
int Hungarian(){
int res = 0;
for(int i=1;i<=n;i++){
memset(vis, 0, sizeof(vis));
if(match(i)) res++;
}
return res;
}
int main(){
cin>>T;
while(T--){
cin>>n;
memset(p, 0, sizeof(p));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
bool a;
cin>>a;
if(a) mat[i][j] = 1;
else mat[i][j] = 0;
}
}
if(Hungarian() == n) cout<<"Yes";
else cout<<"No";
cout<<'\n';
}
return 0;
}
最小覆盖问题
问题描述
找到最少的点,删掉所有包含这些点的边,可以删掉所有边。
算法思路
结论:最小覆盖的点数等于最大匹配数。
找到最小覆盖点集的方法:从左侧一个未匹配成功的点出发,走一趟匈牙利算法的流程(即紫色的箭头),所有左侧未经过的点,和右侧经过的点,即组成最小点覆盖。
例1 vijos1204 CoVH之柯南开锁
题目大意
给定一个由 0
和 1
构成的 n*m
的矩阵,每次操作可以一行或一列的 1
全部变成 0
。问最少经过多少次操作才能使矩阵全 0
。
思路
仿照矩阵游戏建立二分图,每次操作等价于删除所有包含该点的边,此时问题就转化成了求二分图的最小覆盖。
代码
略。