二分图匹配
二分图匹配
首先还是要了解二分图匹配是个什么东西
分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
(以上来自百度百科)说简单一些就是对于一个无向图可以将它分为两个集合,对于同一集合内的元素一定没有边相连,而不同集合内的元素存在边相连,这样的图称之为二分图。
那么二分图匹配呢?
给定一个二分图,寻找与一个边集的边能够满足任意两条边都不依附在同一个顶点,这样叫做二分图的一个匹配。
说的更简单一些也就是让二分图两个集合内的点一个一个对应起来。
通常做题时我们对于一个二分图需要求它的一个最大匹配。也就是尽可能让边集中边的数量尽可能的多,尽可能多的点能够匹配在一起。
匈牙利算法
求解一个二分图的最大匹配,比较简单也是常用的办法就是匈牙利算法。
算法证明比较复杂,所以下面只介绍算法实现的流程。
1.让第一个集合中的点去匹配
2.如果匹配到的点没有配对,那么就暂时先和这个点匹配起来
3.如果匹配到的点已经配对了,那么尝试让与匹配到的这个点匹配的点再去匹配,如果能匹配到,则当前点和匹配到的点就匹配到一起,否则就不能。
4.依次再对集合中的下一个点进行这样的匹配操作。
需要注意的是:每一轮操作中,一个点只能被匹配一次。
例题
luogu 2055假期的宿舍 (一道板子题就不多说了x
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
int read(){
int a=0,f=0;char p=getchar();
while(!isdigit(p)){f|=p=='-';p=getchar();}
while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=getchar();}
return f?-a:a;
}
void print(int x){
if(x<0)putchar('-');x=-x;
if(x>=10)print(x/10);
putchar(x%10+'0');
}
int flag1[60],flag2[60];
int n;
vector<int> m[100];
bool gx[100][100];
bool vis[100];
int bf[100];
bool dfs(int x){
for(int i=0;i<m[x].size();i++){
int j=m[x][i];
if(vis[j])continue;
vis[j]=1;
if(!bf[j]||dfs(bf[j])){
bf[j]=x;
return true;
}
}
return false;
}
int main(){
int t;
t=read();
while(t--){
int cnt=0;
memset(flag1,0,sizeof(flag1));
memset(flag2,0,sizeof(flag2));
memset(m,0,sizeof(m));
memset(gx,0,sizeof(gx));
memset(bf,0,sizeof(bf));
n=read();
for(int i=1;i<=n;i++)
flag1[i]=read();//如果是1表示是在校学生
for(int i=1;i<=n;i++){
flag2[i]=read();//1表示这个在校学生要回家睡觉
if(flag1[i]==0)flag2[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
gx[i][j]=read();
if(gx[i][j]){
if(flag1[j])m[i].push_back(j);//如果j在学校有床
}
if(i==j&&flag1[i])m[i].push_back(j);
}
}
for(int i=1;i<=n;i++){
if((!flag1[i])||(!flag2[i])){//不是在学生或者他不回家睡觉
memset(vis,0,sizeof(vis));
if(!dfs(i))cnt++;
}
}
if(cnt)cout<<"T_T"<<endl;
else cout<<"^_^"<<endl;
}
}