[学习笔记]二分图匹配与匈牙利算法
前言
今天模拟赛T1二分图匹配板子题,但是我不会,于是就全场就我没AT1系列了,赶紧补坑
算法
主要了解两个概念"交替路","增广路".我们所做的就是不断找增广路.图我太懒不想画...推荐一个我认为写的很好的一篇博客,我就是在这学的
https://www.renfei.org/blog/bipartite-matching.html
定理
-
最小点覆盖数等于二分图最大匹配数
证明见Matrix67大神Blog:http://www.matrix67.com/blog/archives/116
-
最大独立集点数数(点集不包含任何一条边)等于总点数减去二分图最大匹配数
-
DAG上的最小不相交路径覆盖(找出最少的经过顶点各不同的路径覆盖整个图)数等于点数减去最大匹配数
这些还是比较有用的
代码
BFS版本
int pre[maxn],match[maxn],vis[maxn];
inline void hungarian(){
queue <int>q;
int s,t,u,v,tmp;
bool flag=0;
for(ri i=1;i<=n*2;i++)pre[i]=match[i]=-1,vis[i]=0;
for(ri o=1;o<=n;o++){
if(match[o]!=-1)continue;
while(q.size())q.pop();
pre[o]=-1,q.push(o),flag=0;
while(q.size()&&!flag){
u=q.front();q.pop();
for(ri i=h[u];i&&!flag;i=edge[i].ne){//注意退出
v=edge[i].to;
if(vis[v]==o)continue;
vis[v]=o;
if(match[v]!=-1){
q.push(match[v]);
pre[match[v]]=u;
}
else{
flag=1;
s=u,t=v;
while(s!=-1){
tmp=match[s];
match[s]=t,match[t]=s;
s=pre[s],t=tmp;
}
}
}
}
if(match[o]!=-1)ans++;
}
printf("%d\n",ans);
return ;
}
注意几点:
-
我们找到一条增广路后要及时特判退出
-
用tmp记录s的另一个匹配
-
一开始起点pre赋为-1
例(水)题:
JZOJ5934 phalanx
经典模型,行为左边的点集,列为右边的,对于不能染两次的行列连边,容易发现连了边的点一起选是非法的,我们要找的就是选出最多的点集使得没有一条边,转化成最大独立集来做
代码:
const int maxn=4005;
const int inf=0x7fffffff;
struct Edge{
int ne,to;
}edge[maxn<<2];
int h[maxn<<1],num_edge=1;
inline void add_edge(int f,int to){
edge[++num_edge].ne=h[f];
edge[num_edge].to=to;
h[f]=num_edge;
}
int match[maxn<<1],pre[maxn<<1];
int vis[maxn<<1];
int n,num;
inline void solve(){
int u,v,s,t,tmp,ans=0;
bool flag=0;
queue <int> q;
for(ri i=1;i<=n*2;i++)vis[i]=0,match[i]=pre[i]=-1;
for(ri o=1;o<=n;o++){
if(match[o]==-1){
while(q.size())q.pop();
q.push(o);
pre[o]=-1,flag=0;
while(q.size()&&!flag){
u=q.front();q.pop();
//vis[u]=1;
for(ri i=h[u];i&&!flag;i=edge[i].ne){
v=edge[i].to;
if(vis[v]==o)continue;
vis[v]=o;
//printf("--%d %d--\n",u,v);
if(match[v]!=-1){
q.push(match[v]);
pre[match[v]]=u;
}
else{
flag=1;
s=u,t=v;
while(s!=-1){
tmp=match[s];
match[s]=t,match[t]=s;
t=tmp,s=pre[s];
}
}
}
}
}
if(match[o]!=-1)ans++;
}
//printf("%d %d\n",n*2,ans);
printf("%d\n",(n*2-ans)*n);
}
int main(){
int x,y;
freopen("phalanx.in","r",stdin);
freopen("phalanx.out","w",stdout);
read(n),read(num);
for(ri i=1;i<=num;i++){
read(x),read(y);
add_edge(x,y+n);
add_edge(y+n,x);
}
solve();
return 0;
}
JZOJ1922 小行星
像上题一样对于小行星所在行列连边,发现任何一条边的左右端点至少选一个,转化成最小点覆盖就好了
代码:
/*
Code By RyeCatcher
*/
const int maxn=2005;
const int inf=0x7ffffff;
struct Edge{
int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=1;
inline void add_edge(int f,int to){
edge[++num_edge].ne=h[f];
edge[num_edge].to=to;
h[f]=num_edge;
}
int n,k,ans=0;
int pre[maxn],match[maxn],vis[maxn];
inline void hungarian(){
queue <int>q;
int s,t,u,v,tmp;
bool flag=0;
for(ri i=1;i<=n*2;i++)pre[i]=match[i]=-1,vis[i]=0;
for(ri o=1;o<=n;o++){
if(match[o]!=-1)continue;
while(q.size())q.pop();
pre[o]=-1,q.push(o),flag=0;
while(q.size()&&!flag){
u=q.front();q.pop();
for(ri i=h[u];i&&!flag;i=edge[i].ne){
v=edge[i].to;
if(vis[v]==o)continue;
vis[v]=o;
if(match[v]!=-1){
q.push(match[v]);
pre[match[v]]=u;
}
else{
flag=1;
s=u,t=v;
while(s!=-1){
tmp=match[s];
match[s]=t,match[t]=s;
s=pre[s],t=tmp;
}
}
}
}
if(match[o]!=-1)ans++;
}
printf("%d\n",ans);
return ;
}
int main(){
read(n),read(k);
int x,y;
for(ri i=1;i<=k;i++){
read(x),read(y);
add_edge(x,n+y);
add_edge(n+y,x);
}
hungarian();
return 0;
}