[学习笔记]二分图匹配与匈牙利算法

前言

今天模拟赛T1二分图匹配板子题,但是我不会,于是就全场就我没AT1系列了,赶紧补坑

算法

主要了解两个概念”交替路”,”增广路”.我们所做的就是不断找增广路.图我太懒不想画…推荐一个我认为写的很好的一篇博客,我就是在这学的

https://www.renfei.org/blog/bipartite-matching.html

定理

这些还是比较有用的

代码

BFS版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

经典模型,行为左边的点集,列为右边的,对于不能染两次的行列连边,容易发现连了边的点一起选是非法的,我们要找的就是选出最多的点集使得没有一条边,转化成最大独立集来做

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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 小行星

像上题一样对于小行星所在行列连边,发现任何一条边的左右端点至少选一个,转化成最小点覆盖就好了

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
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;
}