二分图匹配的几种实现
二分图的最大匹配
二分图的最大匹配有两种方法,匈牙利算法(KM算法)和最大流算法。
我们令现有二分图
- 最大流
实质上是建模,因为你要匹配数最大,且一个点匹配上后就不能再匹配,所以我们设置超级源S和超级汇T,建图的方式如下:
因为二分图每个点只能匹配或被匹配一次,所以容量为1,然后跑出来的最大流即为最大匹配数。
- 匈牙利算法
这个算法是基于一种贪心,每次找增广路,然后它的配对次数至少增加1,多次寻找,最后的复杂度为,点数,为边数。
有两种实现,第一种记录每个点对应配对哪个点,代码实现如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int M=1e6+10;
int n,m,e;
struct ss{
int to,last;
ss(int a=0,int b=0)
:to(a),last(b){}
}g[M<<1];
int head[M],cnt,had[M];
void add(int a,int b){
g[++cnt]=ss(b,head[a]);head[a]=cnt;
g[++cnt]=ss(a,head[b]);head[b]=cnt;
}
bool vis[M];
bool find(int a){
for(int i=head[a];i;i=g[i].last){
if(!vis[g[i].to]){
vis[g[i].to]=1;
if(!had[g[i].to]||find(had[g[i].to])){
had[g[i].to]=a;
return 1;
}
}
}
return 0;
}
void Hungary(){
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
ans+=find(i);
}
printf("%d\n",ans);
}
int u,v;
void readIn(){
scanf("%d%d%d",&n,&m,&e);
while(e--){
scanf("%d%d",&u,&v);
if(v>m||u>n) continue;
add(u,v+n);
}
}
int main(){
readIn();
Hungary();
return 0;
}
第二个为记录点对应哪条边是匹配上的:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=2000010,N=2010;
struct ss{
int to,last;
ss(int a=0,int b=0):to(a),last(b){}
}g[M];
int head[N],cnt=1;
void add(int a,int b){
g[++cnt]=ss(b,head[a]);head[a]=cnt;
g[++cnt]=ss(a,head[b]);head[b]=cnt;
}
int sd[N],vis[N];bool match[N];
bool dfs(int a,bool wc){
if(vis[a]==vis[0]) return 0;
vis[a]=vis[0];
if(!wc){
if(!match[a]){return match[a]=1;}
return dfs(g[sd[a]].to,wc^1);
}
for(int i=head[a];i;i=g[i].last){
if(sd[a]==i||vis[g[i].to]==vis[0]) continue;
if(dfs(g[i].to,wc^1)){
sd[a]=i;sd[g[i].to]=i^1;
return 1;
}
}
return 0;
}
int n,m,e;
int main(){
int a,b;
scanf("%d%d%d",&n,&m,&e);
for(int i=1;i<=e;++i){
scanf("%d%d",&a,&b);
if(a>n||b>m)continue;
add(a,b+n);
}
int bg,ed;
if(n<m)bg=1,ed=n;else bg=n+1,ed=n+m;
int ans=0;
for(int i=bg;i<=ed;++i)++vis[0],match[i]=dfs(i,1),ans+=match[i];
printf("%d\n",ans);
return 0;
}
最大流的方法(用Dinic实现较快):
#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
const int N=3e5+10,M=1e7+10;
const int inf=0x7fffffff;
int n,m,x,y,z,ans,p=1,q,end,e;
int size[N],dis[N],f[N];
vector <int> vec[N];
struct ss{
int to,cap;
ss(int a=0,int b=0):to(a),cap(b){}
}g[M];
void add(int a,int b,int c){
g[++p]=ss(b,c);vec[a].push_back(p);
g[++p]=ss(a,0);vec[b].push_back(p);
}
int bfs(){
memset(dis,0,sizeof(dis));
p=q=1;
dis[0]=1;
f[1]=0;
for(;p<=q;p++){
int v=f[p];
for(int i=0;i<vec[v].size();i++){
int t=vec[v][i];
if(!dis[g[t].to]&&g[t].cap){
dis[g[t].to]=dis[v]+1;
f[++q]=g[t].to;
}
}
}
return dis[end];
}
int dfs(int u,int c){
if(u==end||!c) return c;
int tot=0;
for(int &i=size[u];i<vec[u].size();i++){
int t=vec[u][i];
if(dis[u]+1==dis[g[t].to]&&g[t].cap){
int now=dfs(g[t].to,min(c,g[t].cap));
g[t].cap-=now;
g[t^1].cap+=now;
c-=now;
tot+=now;
if(!c) break;
}
}
return tot;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
#endif
scanf("%d%d%d",&n,&m,&e);
end=n+m+1;
for(int i=1;i<=n;i++) add(0,i,1);
for(int i=1;i<=m;i++) add(i,end,1);
for(int i=1;i<=e;i++){
scanf("%d%d",&x,&y);
if(x>n||y>m) continue;
add(x,y+n,1);
}
while(bfs()){
memset(size,0,sizeof(size));
int k;
while(k=dfs(0,inf)) ans+=k;
}
printf("%d\n",ans-1);
return 0;
}
二分图的带权匹配
模板题目【模板】二分图带权匹配
分为两种:最佳匹配和最大权匹配
最佳匹配是在最大匹配下(也就是点数较少的一边完全匹配)最大的匹配值,而最大权匹配仅仅是权值最大即可。
一般KM算法求的是最佳匹配,而转换为最大权匹配只需将没有直接连边的连一条值为0的边,进行KM算法即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=401;
ll v[M][M],inf;
ll A[M],B[M],ned[M];
int VA[M],VB[M];
int mat[M],now,n,m,e;
bool iscon[M][M];
bool find(int a){
VA[a]=now;
for(int i=1;i<=m;++i){
if(!iscon[a][i]) continue;
if(VB[i]==now) continue;
ll res=A[a]+B[i]-v[a][i];
if(!res){
VB[i]=now;
if(!mat[i]||find(mat[i])){
mat[i]=a;
return 1;
}
}else{
ned[i]=min(ned[i],res);
}
}
return 0;
}
ll KM(){
memset(B,0,sizeof(B));
for(int i=1;i<=n;i++){
A[i]=v[i][1];
for(int j=2;j<=m;j++){
if(v[i][j]>A[i])A[i]=v[i][j];
}
}
for(int i=1;i<=n;i++){
memset(ned,63,sizeof(ned));inf=ned[0];
while(1){
++now;
if(find(i)) break;
ll dec=inf;
for(int j=1;j<=m;j++)
if(VB[j]!=now&&ned[j]<dec)dec=ned[j];
for(int j=1;j<=m;j++){
if(VA[j]==now) A[j]-=dec;
if(VB[j]==now) B[j]+=dec;
else ned[j]-=dec;
}
}
}
ll ans=0;
for(int i=1;i<=m;i++)
ans+=v[mat[i]][i];
return ans;
}
int vis[M],tc;
int a,b;
ll c;
bool flag;
int main(){
scanf("%d%d%d",&n,&m,&e);
if(n>m)swap(n,m),flag=1;
while(e--){
scanf("%d%d%lld",&a,&b,&c);
if(flag)swap(a,b);
v[a][b]=max(v[a][b],c);
iscon[a][b]=1;
}
ll t1=KM();
printf("%lld\n",t1);
memset(iscon,1,sizeof(iscon));
memset(mat,0,sizeof(mat));
t1=KM();
printf("%lld\n",t1);
return 0;
}