二分图匹配

 二分图匹配

基本概念:

给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。

通常分为以下几种匹配:

一、 最大匹配

指在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数。这个问题通常使用匈牙利算法解决,朴素时间复杂度为O(V^3),使用邻接表的前提下时间复杂度为O(VE)。还有一种对匈牙利进行了优化的Hopcroft-Karp算法,时间复杂度为O(sqrt(V)*E)。

二、 完全匹配(完备匹配)

是在最大匹配下的一种特殊情况,指在二分图的两个集合中的点两两都能够得到匹配。

三、 最佳匹配

节点带有权值,在能够完全匹配的前提下,选择匹配方案使得权值之和最大。这个问题通常使用KM算法解决,时间复杂度O(V^3)。

算法介绍:

一、    匈牙利算法

1、基本概念:

1)交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边...形成的路径叫交替路。

2)增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。

如下图所示:

 

红边为三条已经匹配的边。从X部一个未匹配的顶点x4开始,能找到一条增广路:
x4->y3->x2->y1->x1->y2->x4->y3->x2->y1->x1->y2 。

由增广路的定义可以推出下述三个结论:

①增广路的路径长度必定为奇数,第一条边和最后一条边都不属于M(已匹配子图),因为两个端点分属两个集合,且未匹配。

②增广路经过取反操作(已匹配边变为未匹配边,未匹配边变为已匹配边)可以得到一个更大的匹配M’。

③M为G的最大匹配当且仅当不存在相对于M的增广路径。

2、匈牙利算法能解决的问题:

1)最大匹配

最大匹配的匹配边的数目。

2)最小点覆盖数

二分图的最小点覆盖数=最大匹配数(König定理)

这里说明一下什么是最小点覆盖:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边。

3)最小路径覆盖

最小路径覆盖=顶点数-最大匹配数

在一个N*N的有向图中,路径覆盖就是在图中找一些路经,使之覆盖了图中的所有顶点,

且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点。

最小路径覆盖就是找出最小的路径条数,使之成为G的一个路径覆盖。

由上面可以得出:

 ①一个单独的顶点是一条路径;

 ②如果存在一路径p1,p2,......pk,其中p1 为起点,pk为终点,那么在覆盖图中,顶点p1,p2,......pk不再与其它的顶点之间存在有向边。

4)最大独立集

最大独立集=顶点数-最大匹配数。

独立集:图中任意两个顶点都不相连的顶点集合。

3、算法实现(别的博客找的)

我们以下图为例说明匈牙利匹配算法。

 

step1:从1开始,找到右侧的点4,发现点4没有被匹配,所以找到了1的匹配点为4 。得到如下图:

step2:接下来,从2开始,试图在右边找到一个它的匹配点。我们枚举5,发现5还没有被匹配,于是找到了2的匹配点,为5.得到如下图:

step3:接下来,我们找3的匹配点。我们枚举了5,发现5已经有了匹配点2。此时,我们试图找到2除了5以外的另一个匹配点,我们发现,我们可以找到7,于是2可以匹配7,所以5可以匹配给3,得到如下图:

 

此时,结束,我们得到最大匹配为3。

匈牙利算法的本质就是在不断寻找增广路,直到找不到位置则当前的匹配图就是最大匹配。

二、Hopcroft-Carp

这个算法是匈牙利算法的优化,匈牙利算法是一次寻找一条增广路,而它是一次寻找多条增广路,时间复杂度O(sqrt(V)*E)。

一套不解释模板(其实自己也没看懂,算了算了会用就行)。

三、KM算法

时间复杂度O(n^3)

可以参考这些博客:

简洁点的:http://cuitianyi.com/blog/%E6%B1%82%E6%9C%80%E5%A4%A7%E6%9D%83%E4%BA%8C%E5%88%86%E5%8C%B9%E9%85%8D%E7%9A%84km%E7%AE%97%E6%B3%95/

详细点的:http://philoscience.iteye.com/blog/1754498

然后我自己也稍微讲两句吧,按自己理解的(可能会讲错)。

这个算法主要是在原来匈牙利算法的基础上加入了顶标这个东西。

开局是一张二分图对吧,然后对于X和Y集合,分别给每个集合的顶点lx[i]和ly[i]表示每个顶点的顶标,g[i][j]是边(i,j)的权值。

初始化一个二分子图,通过lx[i]+y[i]=g[i][j]这个条件判断边是否在二分子图中,然后每当在当前二分子图找不到匹配时,那就要加边了呀~

怎么加呢,只要在交错路上(没匹配成功的增广路)任意添一条都可以(不懂回去看看匈牙利算法),但是我们要权值最大呀,那我们要尽量亏得少,

于是,我们选择了相比原来权值减得最少的边,损失的差值d=min{lx[i]+ly[j]-w[i][j]}(i是交错路中的点,j是不在交错路中的点)。然后就把交错路的X集合点的顶标-d,

交错路中的Y集合点的顶标+d,这样有什么用呢?为了使得二分子图能够多出新的边(至少多一条,即对应d值得边)。差不多就是贪心每次加边亏得最少的边感觉。

贴模板:

匈牙利算法(51Nod 2006 飞行员配对

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int N=105;

int n,m;                //n为顶点数、m位边数
int link[N];            //link[y]=x,即y与x匹配
bool vis[N];
vector<int>v[N];

//用dfs寻找增广路
bool can(int u){
    for(int i=0;i<v[u].size();i++){
        int t=v[u][i];
        if(!vis[t]){
            vis[t]=true;
            if(link[t]==-1||can(link[t])){ //如果t尚未被匹配,或者link[t]即x可以找到其他能够替代的点,则把t点让给u匹配
                link[t]=u;
                return true;
            }
        }
    }
    return false;
}
//返回最大匹配数
int max_match(){
    memset(link,-1,sizeof(link));
    int ans=0;
    for(int i=1;i<=n;i++){
        memset(vis,false,sizeof(vis));
        if(can(i))
            ans++;
    }
    return ans;
}

int main(){
    scanf("%d%d",&n,&m);
    int a,b;
    while(~scanf("%d%d",&a,&b)){
        if(a==-1&&b==-1)
            break;
        v[a].push_back(b);      //a和b有关联
    }
    int t=max_match();
    if(t==0)
        puts("No Solution!");
    else
        printf("%d
",t);
    return 0;
}

多重匹配(HDU 3605 Escape

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<vector>
 5 #include<algorithm>
 6 using namespace std;
 7 const int N=1e5+5;
 8 const int M=15;
 9 
10 int n,m;
11 int link[M][N],lim[M];
12 bool vis[M];
13 vector<int>v[N];
14 
15 bool dfs(int u){
16     for(int i=0;i<v[u].size();i++){
17         int t=v[u][i];
18         if(vis[t]) continue;
19         vis[t]=true;
20         //若t的匹配数量未达到上限,则直接匹配
21         if(link[t][0]<lim[t]){
22             link[t][++link[t][0]]=u;
23             return true;
24         }
25         //寻找增广路
26         for(int i=1;i<=lim[t];i++){
27             if(dfs(link[t][i])){
28                 link[t][i]=u;
29                 return true;
30             }
31         }
32     }
33     return false;
34 }
35 
36 //多重匹配
37 bool mul_match(){
38     for(int i=1;i<=n;i++){
39         memset(vis,false,sizeof(vis));
40         if(!dfs(i)) return false;
41     }
42     return true;
43 }
44 
45 void init(){
46     for(int i=0;i<=n;i++) v[i].clear();
47     for(int i=1;i<=m;i++) link[i][0]=0;
48 }
49 
50 int main(){
51     while(~scanf("%d%d",&n,&m)){
52         init();
53         for(int i=1;i<=n;i++){
54             for(int j=1;j<=m;j++){
55                 int x;
56                 scanf("%d",&x);
57                 if(x) v[i].push_back(j);
58             }
59         }
60         for(int i=1;i<=m;i++){
61             scanf("%d",&lim[i]);
62         }
63         if(mul_match()) puts("YES");
64         else puts("NO");
65     }
66     return 0;
67 }

Hopcroft-Carp(HDU 2389 Rain on your Parade

  1 #include <stdio.h>
  2 #include <algorithm>
  3 #include <iostream>
  4 #include <string.h>
  5 #include <vector>
  6 #include <queue>
  7 using namespace std;
  8 /* *******************************
  9  * 二分图匹配(Hopcroft-Carp算法)
 10  * 复杂度O(sqrt(n)*E)
 11  * 邻接表存图,vector实现
 12  * vector先初始化,然后假入边
 13  * uN 为左端的顶点数,使用前赋值(点编号0开始)
 14  */
 15 const int N = 3030;
 16 const int INF = 0x3f3f3f3f;
 17 vector<int>G[N];
 18 
 19 int uN,dis;
 20 int Mx[N],My[N],dx[N],dy[N];
 21 bool used[N];
 22 
 23 struct gnode{
 24     int x,y,speed;
 25 }guest[N];
 26 
 27 struct unode{
 28     int x,y;
 29 }ubm[N];
 30 
 31 bool SearchP()
 32 {
 33     queue<int>Q;
 34     dis = INF;
 35     memset(dx,-1,sizeof(dx));
 36     memset(dy,-1,sizeof(dy));
 37     for(int i = 0 ; i < uN; i++)
 38         if(Mx[i] == -1)
 39         {
 40             Q.push(i);
 41             dx[i] = 0;
 42         }
 43     while(!Q.empty())
 44     {
 45         int u = Q.front();
 46         Q.pop();
 47         if(dx[u] > dis)break;
 48         int sz = G[u].size();
 49         for(int i = 0;i < sz;i++)
 50         {
 51             int v = G[u][i];
 52             if(dy[v] == -1)
 53             {
 54                 dy[v] = dx[u] + 1;
 55                 if(My[v] == -1)dis = dy[v];
 56                 else
 57                 {
 58                     dx[My[v]] = dy[v] + 1;
 59                     Q.push(My[v]);
 60                 }
 61             }
 62         }
 63     }
 64     return dis != INF;
 65 }
 66 bool DFS(int u)
 67 {
 68     int sz = G[u].size();
 69     for(int i = 0;i < sz;i++)
 70     {
 71         int v = G[u][i];
 72         if(!used[v] && dy[v] == dx[u] + 1)
 73         {
 74             used[v] = true;
 75             if(My[v] != -1 && dy[v] == dis)continue;
 76             if(My[v] == -1 || DFS(My[v]))
 77             {
 78                 My[v] = u;
 79                 Mx[u] = v;
 80                 return true;
 81             }
 82         }
 83     }
 84     return false;
 85 }
 86 
 87 int max_match()
 88 {
 89     int res = 0;
 90     memset(Mx,-1,sizeof(Mx));
 91     memset(My,-1,sizeof(My));
 92     while(SearchP())
 93     {
 94         memset(used,false,sizeof(used));
 95         for(int i = 0;i < uN;i++)
 96             if(Mx[i] == -1 && DFS(i))
 97                 res++;
 98     }
 99     return res;
100 }
101 
102 int main(){
103     int q,cas=0;
104     scanf("%d",&q);
105     while(q--){
106         int time,n,m;
107         scanf("%d%d",&time,&m);
108         for(int i=0;i<=m;i++) G[i].clear();
109         for(int i=0;i<m;i++){
110             scanf("%d%d%d",&guest[i].x,&guest[i].y,&guest[i].speed);
111         }
112         scanf("%d",&n);
113         for(int i=0;i<n;i++){
114             scanf("%d%d",&ubm[i].x,&ubm[i].y);
115         }
116         for(int i=0;i<m;i++){
117             for(int j=0;j<n;j++){
118                 gnode t1=guest[i];
119                 unode t2=ubm[j];
120                 int dis=abs(t1.x-t2.x)+abs(t1.y-t2.y);
121                 if(1.0*dis/t1.speed<=time) G[i].push_back(j);
122             }
123         }
124         uN=m;
125         printf("Scenario #%d:
%d

",++cas,max_match());
126     }
127     return 0;
128 }

KM算法(HDU 2255 奔小康赚大钱

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N=3e3+5;
 7 const int INF=0x3f3f3f3f;
 8 
 9 int nx,ny,d; //nx,ny对应两个集合的点数
10 int link[N],lx[N],ly[N];//lx,ly为顶标,nx,ny为x点集y点集的个数
11 int g[N][N];            //g[i][j]表示集合X中的i点到集合Y中的j点的距离
12 bool visx[N],visy[N];
13 
14 bool dfs(int x){
15     visx[x]=true;
16     for(int y=0;y<ny;y++){
17         if(visy[y]) continue;
18         int tmp=lx[x]+ly[y]-g[x][y];
19         //tmp=0表示边(x,y)在当前子图中
20         if(tmp==0){
21             visy[y]=true;
22             if(link[y]==-1||dfs(link[y])){
23                 link[y]=x;
24                 return true;
25             }
26         }
27         else d=min(tmp,d);  //d取最小
28     }
29     return false;
30 }
31 
32 int KM(){
33     //初始化
34     memset(link,-1,sizeof(link));
35     memset(ly,0,sizeof(ly));
36     for(int i=0;i<nx;i++){
37         lx[i]=-INF;
38         for(int j=0;j<ny;j++){
39             if(g[i][j]>lx[i])
40                 lx[i]=g[i][j];
41         }
42     }
43 
44     for(int x=0;x<nx;x++){
45         while(true){
46             memset(visx,false,sizeof(visx));
47             memset(visy,false,sizeof(visy));
48             d=INF;
49             if(dfs(x)) break;
50             //如果找不到增广路,则通过更改顶标,将新的边加入子图
51             //d=min{lx[i]+ly[i]-w[i][j]}
52             for(int i=0;i<nx;i++){
53                 if(visx[i])
54                     lx[i]-=d;
55             }
56             for(int i=0;i<ny;i++){
57                 if(visy[i])
58                     ly[i]+=d;
59             }
60         }
61     }
62     int res=0;
63     for(int i=0;i<ny;i++){
64         if(link[i]!=-1)
65             res+=g[link[i]][i];
66     }
67     return res;
68 }
69 
70 int main(){
71     int n;
72     while(~scanf("%d",&n)){
73         for(int i=0;i<n;i++){
74             for(int j=0;j<n;j++){
75                 scanf("%d",&g[i][j]);
76             }
77         }
78         nx=ny=n;
79         printf("%d
",KM());
80     }
81     return 0;
82 }
原文地址:https://www.cnblogs.com/fu3638/p/8784826.html