• 二分图匹配


     二分图匹配

    基本概念:

    给定一个二分图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 }
  • 相关阅读:
    [转帖]一些理念,一份感动,一段友谊。或许在短暂的一生中就弥足珍贵了。
    浏览器屏蔽双击选中文字
    RBAC: 基于角色的访问控制(Role-Based Access Control)
    关于ehcache配置中timeToLiveSeconds和timeToIdleSeconds的区别
    拓展jQuery的方法
    用HashSet存储不重复的对象
    spring task 实现定时执行(补充:解决定时任务执行2次问题)
    CronExpression
    java使用POI实现excel文件的读取,兼容后缀名xls和xlsx
    jquery的原理机制
  • 原文地址:https://www.cnblogs.com/fu3638/p/8784826.html
Copyright © 2020-2023  润新知