二分图的最大匹配

    二分图指的是这样一种图,其所有顶点可以分成两个集合X和Y,其中X或Y中任意两个在同一集合中的点都不相连,所有的边关联在两个顶点中,恰好一个属于集合X,另一个属于集合Y。给定一个二分图G,M为G边集的一个子集,如果M满足当中的任意两条边都不依附于同一个顶点,则称M是一个匹配。图中包含边数最多的匹配称为图的最大匹配。

     二分图的最大匹配有两种求法,第一种是最大流;第二种就是我现在要讲的匈牙利算法。这个算法说白了就是最大流的算法,但是它跟据二分图匹配这个问题的特点,把最大流算法做了简化,提高了效率。

      增广路径的定义(也称增广轨或交错轨):
  若P是图G中一条连通两个未匹配顶点的路径,并且属M的边和不属M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
由增广路径的定义可以推出下述4个结论:
        1-P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
        2-P上所有第奇数条边都不在M中,所有第偶数条边都出现在M中。
   3-P经过取反操作可以得到一个更大的匹配M’。所谓“取反”即把P上所有第奇数条边(原不在M中)加入到M中,并把P中所有第偶数条边(原在M中)从M中删除,则新的匹配数就比原匹配数多了1个。(增广路顾名思义就是使匹配数增多的路径)
        4-M为G的最大匹配当且仅当不存在相对于M的增广路径。

   最大流算法的核心问题就是找增广路径(augment path)。匈牙利算法也不例外,它的基本模式就是:
     初始时最大匹配为空
      while 找得到增广路径
               do 把增广路径加入到最大匹配中去
      可见和最大流算法是一样的。但是这里的增广路径就有它一定的特殊性。(注:匈牙利算法虽然根本上是最大流算法,但是它不需要建网络模型,所以图中不再需要源点和汇点,仅仅是一个二分图。每条边也不需要有方向。)

      算法的思路是不停的找增广路径, 并增加匹配的个数,增广路径顾名思义是指一条可以使匹配数变多的路径,在匹配问题中,增广路径的表现形式是一条"交错路径",也就是说这条由图的边组成的路径, 它的第一条边是目前还没有参与匹配的,第二条边参与了匹配,第三条边没有..最后一条边没有参与匹配,并且始点和终点还没有被选择过。这样交错进行,显然他有奇数条边。那么对于这样一条路径,我们可以将第一条边改为已匹配,第二条边改为未匹配...以此类推。也就是将所有的边进行"反色",容易发现这样修改以后,匹配仍然是合法的,但是匹配数增加了一对。另外,单独的一条连接两个未匹配点的边显然也是交错路径。可以证明。当不能再找到增广路径时,就得到了一个最大匹配,这也就是匈牙利算法的思路。

3个重要结论:

最小点覆盖数: 最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。可以证明:最少的点(即覆盖数)=最大匹配数
最小路径覆盖=最小路径覆盖=|N|-最大匹配数
用尽量少的不相交简单路径覆盖有向无环图G的所有结点。解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。
二分图最大独立集=顶点数-二分图最大匹配
在N个点的图G中选出m个点,使这m个点两两之间没有边,求m最大值。
如果图G满足二分图条件,则可以用二分图匹配来做.最大独立集点数 = N - 最大匹配数。


例题:

http://poj.org/problem?id=1469  COURSES

有了匈牙利算法的基础,该题就是一道非常简单的题目了:该题给出P门课程,N个学生,问能否从中选出P个学生,使每个学生上不同的课,且每个课程有一个学生。典型的二分图匹配的问题,我们只要计算最大二分图匹配数,如果和课程数相同就输出YES,否则输出NO。

[cpp]  view plain  copy
  1. //poj_1469  
  2. /*==================================================*\ 
  3. | 二分图匹配(匈牙利算法DFS 实现) 
  4. | INIT: g[][]邻接矩阵; 
  5. | 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。 
  6. | 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE) 
  7. ==================================================*/  
  8. #include<stdio.h>  
  9. #include<memory.h>  
  10.   
  11. bool g[110][310]; //邻接矩阵,true代表有边相连  
  12. bool flag,visit[310];    //记录V2中的某个点是否被搜索过  
  13. int match[310];   //记录与V2中的点匹配的点的编号  
  14. int p,n;   //二分图中左边、右边集合中顶点的数目   
  15.   
  16. // 匈牙利算法  
  17. bool dfs(int u)  
  18. {  
  19.     for (int i = 1; i <= n; ++i)  
  20.     {  
  21.         if (g[u][i] && !visit[i])   //如果节点i与u相邻并且未被查找过  
  22.         {  
  23.             visit[i] = true;   //标记i为已查找过  
  24.             if (match[i] == -1 || dfs(match[i]))   //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径  
  25.             {  
  26.                 match[i] = u;  //记录查找成功记录,更新匹配M(即“取反”)  
  27.                 return true;   //返回查找成功  
  28.             }  
  29.         }  
  30.     }  
  31.     return false;  
  32. }  
  33.   
  34. int main(void)  
  35. {  
  36.     int i,j,k,t,v,ans;  
  37.     scanf("%d",&t);  
  38.     while (t--)  
  39.     {  
  40.           scanf("%d %d", &p, &n);  
  41.           for (i = 1; i <= p; i++)  
  42.           {  
  43.               for (j = 1; j <= n; j++)  
  44.                   g[i][j] = false;  
  45.           }  
  46.           for (i = 1; i <= n; i++)  
  47.               match[i] = -1;  
  48.           flag = true;  
  49.           for (i = 1; i <= p; i++)  
  50.           {  
  51.               scanf("%d",&k);  
  52.               if (k == 0)  
  53.                  flag = false;  
  54.               while (k--)  
  55.               {  
  56.                     scanf("%d",&v);  
  57.                     g[i][v]  = true;  
  58.               }  
  59.           }  
  60.           if (flag)  
  61.           {  
  62.                ans = 0;  
  63.                for (i = 1; i <= p; i++)  
  64.                {  
  65.                    memset(visit,false,sizeof(visit));   //清空上次搜索时的标记  
  66.                    if( dfs(i) )    //从节点i尝试扩展  
  67.                        ans++;  
  68.                }  
  69.                if (ans == p)  
  70.                    puts("YES");  
  71.                else  
  72.                    puts("NO");  
  73.           }   
  74.           else  
  75.               puts("NO");  
  76.     }  
  77.       
  78.     return 0;  
  79. }  

pku 2446 二分图最大匹配的应用

http://poj.org/problem?id=2446   Chessboard

题意:给出一个矩形N*M棋盘,有K个格子是空洞,然后用2*1的矩形,对所有非空洞的格子进行覆盖,如果可以全部覆盖,就puts("YES");
算法:建立二分图,用匈牙利算法;
我们分别对所有的格子进行标号1.。。N*M
将问题转化为二分图最大匹配问题。将棋盘按国际象棋棋盘那样添上黑白两种颜色,这样的话,黑色和白色的格子就构成了二分图的两个集合,即相邻的两个格子不会属于同个集合的。然后从上到下,从左到右对格子进行编号(除了洞),相邻的两格用边相连就构成一个二分图。然后求出最大匹配。。如果最大匹配+K=N*M就输出YES。。

二分图建图就是对于每一个不是洞的点,往4个方向扩展,如果哪个方向有不是洞的点,那么就可以连上一条边,然后我们再求这个二分图的最大匹配,然后判断它是否是一个完备匹配(即所有点都在匹配边上的匹配)
二分图永远是单向的,本题中的二分图中的x和y是一样的,但是即使这样也不能认为这个二分图是双向的,在本题通过上面的方法建图以后,我们只要求出最大独立集的个数是不是等于洞的个数,或者判断这个二分图是不是完备的就行了。

[cpp]  view plain  copy
  1. //poj_2446  
  2. /*==================================================*\ 
  3. | 二分图匹配(匈牙利算法DFS 实现) 
  4. | INIT: g[][]邻接矩阵; 
  5. | 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。 
  6. | 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE) 
  7. ==================================================*/  
  8. #include<stdio.h>  
  9. #include<memory.h>  
  10.   
  11. #define MAX 1089 //33*33  
  12. bool g[MAX][MAX]; //邻接矩阵,true代表有边相连  
  13. bool flag,visit[MAX];    //记录V2中的某个点是否被搜索过  
  14. int match[MAX];   //记录与V2中的点匹配的点的编号  
  15. int cnt;   //二分图中左边、右边集合中顶点的数目  
  16. bool hole[MAX][MAX];  
  17. int id[MAX][MAX];  
  18.   
  19. // 匈牙利算法  
  20. bool dfs(int u)  
  21. {  
  22.     for (int i = 1; i <= cnt; ++i)  
  23.     {  
  24.         if (g[u][i] && !visit[i])   //如果节点i与u相邻并且未被查找过  
  25.         {  
  26.             visit[i] = true;   //标记i为已查找过  
  27.             if (match[i] == -1 || dfs(match[i]))   //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径  
  28.             {  
  29.                 match[i] = u;  //记录查找成功记录,更新匹配M(即“取反”)  
  30.                 return true;   //返回查找成功  
  31.             }  
  32.         }  
  33.     }  
  34.     return false;  
  35. }  
  36. int MaxMatch()  
  37. {  
  38.     int i,sum=0;  
  39.     memset(match,-1,sizeof(match));  
  40.     for(i = 1 ; i <= cnt ; ++i)  
  41.     {  
  42.         memset(visit,false,sizeof(visit));   //清空上次搜索时的标记  
  43.         if( dfs(i) )    //从节点i尝试扩展  
  44.         {  
  45.             sum++;  
  46.         }  
  47.     }  
  48.     return sum;  
  49. }  
  50.   
  51. int main(void)  
  52. {  
  53.     int i,j,k,m,n,ans,y,x;  
  54.     while (scanf("%d %d %d",&m,&n,&k)!=EOF)  
  55.     {  
  56.           memset(g,false,sizeof(g));  
  57.           memset(hole,false,sizeof(hole));  
  58.           for (i = 1; i <= k; ++i)  
  59.           {  
  60.               scanf("%d %d",&y,&x);  
  61.               hole[x][y] = true;  
  62.           }  
  63.           if((m*n-k)&1)   //奇偶剪枝  
  64.           {  
  65.               puts("NO");  
  66.               continue;  
  67.           }  
  68.           cnt = 0;  
  69.   
  70.           for (i = 1; i <= m; ++i)  
  71.           {  
  72.               for (j = 1; j <= n; ++j)  
  73.               {  
  74.                   if(hole[i][j] == false)   //对没有涂黑的点进行标号  
  75.                   {  
  76.                       id[i][j] = ++cnt;  
  77.                   }  
  78.               }  
  79.           }  
  80.           for (i = 1; i <= m; ++i)  
  81.           {  
  82.               for (j = 1; j <= n; ++j)  
  83.               {  
  84.                   if(hole[i][j] == false)  
  85.                   {  
  86.                       if(i-1>0 && hole[i-1][j] == false)   //建图。。要注意边界问题  
  87.                           g[ id[i][j] ][ id[i-1][j] ] = true;  
  88.                       if(i+1<=m && hole[i+1][j] == false)  
  89.                           g[ id[i][j] ][ id[i+1][j] ] = true;  
  90.                       if(j-1>0 && hole[i][j-1] == false)  
  91.                           g[ id[i][j] ][ id[i][j-1] ] = true;  
  92.                       if(j+1<=n && hole[i][j+1] == false)  
  93.                           g[ id[i][j] ][ id[i][j+1] ] = true;  
  94.                   }  
  95.               }  
  96.           }  
  97.   
  98.           ans = MaxMatch();  
  99.           if (ans == cnt)  
  100.               puts("YES");  
  101.           else  
  102.               puts("NO");  
  103.     }  
  104.       
  105.     return 0;  
  106. }  

静态邻接表模板:

[cpp]  view plain  copy
  1. //poj_2446  
  2. /*==================================================*\ 
  3. | 二分图匹配(匈牙利算法DFS 实现) 
  4. | 邻接表方法来实现; 
  5. | 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。 
  6. | 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE) 
  7. ==================================================*/  
  8. #include<stdio.h>  
  9. #include<memory.h>  
  10.   
  11. #define MAX 1089 //33*33  
  12. bool flag,visit[MAX];    //记录V2中的某个点是否被搜索过  
  13. int match[MAX];   //记录与V2中的点匹配的点的编号  
  14. int cnt;   //二分图中左边、右边集合中顶点的数目  
  15. bool hole[MAX][MAX];  
  16. int id[MAX][MAX];  
  17. int head[MAX];  
  18.   
  19. struct edge  
  20. {  
  21.     int to,next;  
  22. }e[100005];  
  23. int index;  
  24.   
  25. void addedge(int u,int v)  
  26. {   //向图中加边的算法,注意加上的是有向边  
  27.     //u为v的后续节点既是v---->u  
  28.     e[index].to=v;  
  29.     e[index].next=head[u];  
  30.     head[u]=index;  
  31.     index++;  
  32. }  
  33.   
  34. // 匈牙利(邻接表)算法  
  35. bool dfs(int u)  
  36. {  
  37.     int i,v;  
  38.     for(i = head[u]; i != 0; i = e[i].next)  
  39.     {  
  40.         v = e[i].to;  
  41.         if(!visit[v])   //如果节点v与u相邻并且未被查找过  
  42.         {  
  43.             visit[v] = true;   //标记v为已查找过  
  44.             if(match[v] == -1 || dfs(match[v]))   //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径  
  45.             {  
  46.                 match[v] = u;  //记录查找成功记录,更新匹配M(即“取反”)  
  47.                 return true;   //返回查找成功  
  48.             }  
  49.         }  
  50.     }  
  51.     return false;  
  52. }  
  53. int MaxMatch()  
  54. {  
  55.     int i,sum=0;  
  56.     memset(match,-1,sizeof(match));  
  57.     for(i = 1 ; i <= cnt ; ++i)  
  58.     {  
  59.         memset(visit,false,sizeof(visit));   //清空上次搜索时的标记  
  60.         if( dfs(i) )    //从节点i尝试扩展  
  61.         {  
  62.             sum++;  
  63.         }  
  64.     }  
  65.     return sum;  
  66. }  
  67.   
  68. int main(void)  
  69. {  
  70.     int i,j,k,m,n,ans,y,x;  
  71.     while (scanf("%d %d %d",&m,&n,&k)!=EOF)  
  72.     {  
  73.           memset(hole,false,sizeof(hole));  
  74.           for (i = 1; i <= k; ++i)  
  75.           {  
  76.               scanf("%d %d",&y,&x);  
  77.               hole[x][y] = true;  
  78.           }  
  79.           if((m*n-k)&1)   //奇偶剪枝  
  80.           {  
  81.               puts("NO");  
  82.               continue;  
  83.           }  
  84.           cnt = 0;  
  85.           index = 1;  
  86.   
  87.           for (i = 1; i <= m; ++i)  
  88.           {  
  89.               for (j = 1; j <= n; ++j)  
  90.               {  
  91.                   if(hole[i][j] == false)   //对没有涂黑的点进行标号  
  92.                   {  
  93.                       id[i][j] = ++cnt;  
  94.                   }  
  95.               }  
  96.           }  
  97.           memset(head,0,sizeof(head));    //切记要初始化  
  98.           for (i = 1; i <= m; ++i)  
  99.           {  
  100.               for (j = 1; j <= n; ++j)  
  101.               {  
  102.                   if(hole[i][j] == false)  
  103.                   {  
  104.                       if(i-1>0 && hole[i-1][j] == false)   //建图。。要注意边界问题  
  105.                           addedge(id[i][j],id[i-1][j]);  
  106.                       if(i+1<=m && hole[i+1][j] == false)  
  107.                           addedge(id[i][j],id[i+1][j]);  
  108.                       if(j-1>0 && hole[i][j-1] == false)  
  109.                           addedge(id[i][j],id[i][j-1]);  
  110.                       if(j+1<=n && hole[i][j+1] == false)  
  111.                           addedge(id[i][j],id[i][j+1]);  
  112.                   }  
  113.               }  
  114.           }  
  115.   
  116.           ans = MaxMatch();  
  117.           if (ans == cnt)  
  118.               puts("YES");  
  119.           else  
  120.               puts("NO");  
  121.     }  
  122.     return 0;  
  123. }  
http://poj.org/problem?id=1274

题意描述:
农夫约翰上个星期刚刚建好了他的新牛棚,他使用了最新的挤奶技术。不幸的是,由于工程问题,每个牛栏都不一样。第一个星期,农夫约翰随便地让奶牛们进入牛栏,但是问题很快地显露出来:每头奶牛都只愿意在她们喜欢的那些牛栏中产奶。上个星期,农夫约翰刚刚收集到了奶牛们的爱好的信息(每头奶牛喜欢在哪些牛栏产奶)。一个牛栏只能容纳一头奶牛,当然,一头奶牛只能在一个牛栏中产奶。
输入:第一行 两个整数,N (0 <= N <= 200) 和 M (0 <= M <= 200) 。N 是农夫约翰的奶牛数量,M 是新牛棚的牛栏数量。
第二行到第N+1行 一共 N 行,每行对应一只奶牛。第一个数字 (Si) 是这头奶牛愿意在其中产奶的牛栏的数目 (0 <= Si <= M) 。后面的 Si 个数表示这些牛栏的编号。牛栏的编号限定在区间 (1..M) 中,在同一行,一个牛栏不会被列出两次。
输出:
只有一行。输出一个整数,表示最多能分配到的牛栏的数量。

给出奶牛们的爱好的信息,计算最大分配方案。 

[cpp]  view plain  copy
  1. #include<stdio.h>  
  2. #include<memory.h>  
  3.   
  4. #define MAX 202  
  5. bool flag,visit[MAX];    //记录V2中的某个点是否被搜索过  
  6. int match[MAX];   //记录与V2中的点匹配的点的编号  
  7. int cow, stall;   //二分图中左边、右边集合中顶点的数目  
  8. int head[MAX];  
  9.   
  10. struct edge  
  11. {  
  12.     int to,next;  
  13. }e[3000];  
  14. int index;  
  15.   
  16. void addedge(int u,int v)  
  17. {   //向图中加边的算法,注意加上的是有向边  
  18.     //u为v的后续节点既是v---->u  
  19.     e[index].to=v;  
  20.     e[index].next=head[u];  
  21.     head[u]=index;  
  22.     index++;  
  23. }  
  24.   
  25.   
  26. // 匈牙利(邻接表)算法  
  27. bool dfs(int u)  
  28. {  
  29.     int i,v;  
  30.     for(i = head[u]; i != 0; i = e[i].next)  
  31.     {  
  32.         v = e[i].to;  
  33.         if(!visit[v])   //如果节点v与u相邻并且未被查找过  
  34.         {  
  35.             visit[v] = true;   //标记v为已查找过  
  36.             if(match[v] == -1 || dfs(match[v]))   //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径  
  37.             {  
  38.                 match[v] = u;  //记录查找成功记录,更新匹配M(即“取反”)  
  39.                 return true;   //返回查找成功  
  40.             }  
  41.         }  
  42.     }  
  43.     return false;  
  44. }  
  45. int MaxMatch()  
  46. {  
  47.     int i,sum=0;  
  48.     memset(match,-1,sizeof(match));  
  49.     for(i = 1 ; i <= cow ; ++i)  
  50.     {  
  51.         memset(visit,false,sizeof(visit));   //清空上次搜索时的标记  
  52.         if( dfs(i) )    //从节点i尝试扩展  
  53.         {  
  54.             sum++;  
  55.         }  
  56.     }  
  57.     return sum;  
  58. }  
  59.   
  60. int main(void)  
  61. {  
  62.     int i,j,k,ans,m;  
  63.     while (scanf("%d %d",&cow, &stall)!=EOF)  
  64.     {  
  65.         memset(head,0,sizeof(head));    //切记要初始化  
  66.         index = 1;  
  67.         for (i = 1; i <= cow; ++i)  
  68.         {  
  69.             scanf("%d",&k);  
  70.             for (j = 0; j < k; ++j)  
  71.             {  
  72.                 scanf("%d",&m);  
  73.                 addedge(i , m);  
  74.             }  
  75.         }  
  76.   
  77.         ans = MaxMatch();  
  78.         printf("%d\n",ans);  
  79.     }  
  80.     return 0;  
  81. }  
http://poj.org/problem?id=1325

分析:显然,机器重启次数是两台机器需要使用的不同模式的个数。把每个任务看成一条边,即A机器的每个模式看成一个X节点,B机器的每个模式看成一个Y节点,任务i为边(ai, bi)。本题即求最少的点让每条边至少与其中的一点关联,即求一个点的最小覆盖。可以证明,这个最小覆盖就是该二分图的最大匹配数。故二分图匹配的模型就建好了。注意到开始时机器都处于0模式,所以如果某个任务可以在0模式下执行,则我们可以不考虑该任务,假定它已经被完成即可,也就是建图的时候不要把与0关联的边加到二分图中就可以得到正确的解。

[cpp]  view plain  copy
  1. #include<stdio.h>  
  2. #include<memory.h>  
  3.   
  4. #define MAX 105  
  5. bool visit[MAX];    //记录V2中的某个点是否被搜索过  
  6. int match[MAX];   //记录与V2中的点匹配的点的编号  
  7. int n,m;   //二分图中左边、右边集合中顶点的数目  
  8. int head[MAX];  
  9.   
  10. struct edge  
  11. {  
  12.     int to,next;  
  13. }e[1005];  
  14. int index;  
  15.   
  16. void addedge(int u,int v)  
  17. {   //向图中加边的算法,注意加上的是有向边  
  18.     //u为v的后续节点既是v---->u  
  19.     e[index].to=v;  
  20.     e[index].next=head[u];  
  21.     head[u]=index;  
  22.     index++;  
  23. }  
  24.   
  25. // 匈牙利(邻接表)算法  
  26. bool dfs(int u)  
  27. {  
  28.     int i,v;  
  29.     for(i = head[u]; i != 0; i = e[i].next)  
  30.     {  
  31.         v = e[i].to;  
  32.         if(!visit[v])   //如果节点v与u相邻并且未被查找过  
  33.         {  
  34.             visit[v] = true;   //标记v为已查找过  
  35.             if(match[v] == -1 || dfs(match[v]))   //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径  
  36.             {  
  37.                 match[v] = u;  //记录查找成功记录,更新匹配M(即“取反”)  
  38.                 return true;   //返回查找成功  
  39.             }  
  40.         }  
  41.     }  
  42.     return false;  
  43. }  
  44. int MaxMatch()  
  45. {  
  46.     int i,sum=0;  
  47.     memset(match,-1,sizeof(match));  
  48.     for(i = 1 ; i < n ; ++i)  
  49.     {  
  50.         memset(visit,false,sizeof(visit));   //清空上次搜索时的标记  
  51.         if( dfs(i) )    //从节点i尝试扩展  
  52.         {  
  53.             sum++;  
  54.         }  
  55.     }  
  56.     return sum;  
  57. }  
  58.   
  59. int main(void)  
  60. {  
  61.     int i,j,k,ans,y,x;  
  62.     while (scanf("%d",&n),n)  
  63.     {  
  64.         scanf("%d %d",&m,&k);  
  65.         index = 1;  
  66.         memset(head,0,sizeof(head));    //切记要初始化  
  67.   
  68.         for (i = 1; i <= k; ++i)  
  69.         {  
  70.             scanf("%d %d %d",&j,&x,&y);  
  71.             if(x && y)  
  72.                 addedge(x,y);  
  73.         }  
  74.         ans = MaxMatch();  
  75.         printf("%d\n",ans);  
  76.     }  
  77.     return 0;  
  78. }  

动态邻接表模板:

[cpp]  view plain  copy
  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. int n,m,k;  
  5. bool visit[105];    //每次找增广路时对Y中点是否访问  
  6. int match[105];   //Y中点匹配的X中点的位置  
  7.   
  8. struct edge  
  9. {  
  10.     int from;  
  11.     int to;  
  12.     edge* next;  
  13.     edge()  
  14.     {  
  15.         from = to = 0;  
  16.         next = NULL;  
  17.     }  
  18. };  
  19. edge* List[105];  
  20.   
  21. void add_edge(int f,int t)  
  22. {  
  23.     edge* node = new edge();  
  24.     node->from = f;  
  25.     node->to = t;  
  26.     node->next = List[f];  
  27.     List[f] = node;  
  28. }  
  29.   
  30. // 匈牙利(邻接表)算法    
  31. bool dfs(edge* node)  
  32. {  
  33.     int i;    
  34.     for(; node != NULL; node = node->next)    
  35.     {    
  36.         i = node->to;   
  37.         if(visit[i] == NULL)   //如果节点v与u相邻并且未被查找过    
  38.         {    
  39.             visit[i] = true;   //标记v为已查找过    
  40.             if(match[i] == -1 || dfs( List[match[i]] ) )   //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径    
  41.             {    
  42.                 match[i] = node->from;  //记录查找成功记录,更新匹配M(即“取反”)    
  43.                 return true;   //返回查找成功    
  44.             }    
  45.         }    
  46.     }    
  47.     return false;    
  48. }  
  49.   
  50. int main(void)  
  51. {  
  52.     int i,x,y;  
  53.     while(scanf("%d",&n)!=EOF && n!=0)  
  54.     {  
  55.         for(i=0;i<=n;i++)//链表清空,一开始没清空,错了很多次  
  56.         {  
  57.             List[i] = NULL;  
  58.         }  
  59.         memset(match,-1,sizeof(match));  
  60.         scanf("%d%d",&m,&k);  
  61.         while(k--)  
  62.         {  
  63.             scanf("%d%d%d",&i,&x,&y);  
  64.             if(x && y)  
  65.                 add_edge(x,y);  
  66.         }  
  67.         int sum = 0;  
  68.         for(i=1;i<=n;i++)  
  69.         {  
  70.             memset(visit,false,sizeof(visit));  
  71.             if( dfs(List[i]) )  
  72.                 sum++;  
  73.         }  
  74.         printf("%d\n",sum);  
  75.     }  
  76.     return 0;  
  77. }  

http://acm.hdu.edu.cn/showproblem.php?pid=1281棋盘游戏

又是一种建图的方式,对于一个坐标,x , y 可以分成两个不同的集合,如果该点满足某种性质的话,就在 x , y 上连一条线,本题就是这样的……
本题要求关键点,那么只需要对于每个可行点进行删点,然后看看得出的最大匹配是否小于不删点的解,如果小于,则是关键点……统计一下即可。 
代码:

[cpp]  view plain  copy
  1. /*==================================================*\ 
  2. | 二分图匹配(匈牙利算法DFS 实现) 
  3. | INIT: g[][]邻接矩阵; 
  4. | 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。 
  5. | 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE) 
  6. ==================================================*/  
  7. #include<stdio.h>  
  8. #include<memory.h>  
  9.   
  10. bool g[101][101]; //邻接矩阵,true代表有边相连  
  11. bool visit[101];    //记录V2中的某个点是否被搜索过  
  12. int match[101];   //记录与V2中的点匹配的点的编号  
  13. int n,m,k;   //二分图中左边、右边集合中顶点的数目   
  14.   
  15. // 匈牙利算法  
  16. bool dfs(int u)  
  17. {  
  18.     for (int i = 1; i <= m; ++i)  
  19.     {  
  20.         if (g[u][i] && !visit[i])   //如果节点i与u相邻并且未被查找过  
  21.         {  
  22.             visit[i] = true;   //标记i为已查找过  
  23.             if (match[i] == -1 || dfs(match[i]))   //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径  
  24.             {  
  25.                 match[i] = u;  //记录查找成功记录,更新匹配M(即“取反”)  
  26.                 return true;   //返回查找成功  
  27.             }  
  28.         }  
  29.     }  
  30.     return false;  
  31. }  
  32.   
  33. inline int MaxMatch()  
  34. {    
  35.     int i,sum=0;    
  36.     memset(match,-1,sizeof(match));    
  37.     for(i = 1 ; i <= n ; ++i)    
  38.     {    
  39.         memset(visit,false,sizeof(visit));   //清空上次搜索时的标记    
  40.         if( dfs(i) )    //从节点i尝试扩展    
  41.         {    
  42.             sum++;    
  43.         }    
  44.     }    
  45.     return sum;    
  46. }  
  47.   
  48. int main(void)  
  49. {  
  50.     int i,j,ans,x,y,num,t=1;  
  51.     while (scanf("%d %d %d",&n,&m,&k)!=EOF)  
  52.     {  
  53.           memset(g,false,sizeof(g));   //初始化  
  54.           for (i = 1; i <= k; ++i)  
  55.           {  
  56.               scanf("%d %d",&x,&y);  
  57.               g[x][y] = true;  
  58.           }  
  59.           ans = MaxMatch();  
  60.           num = 0;  
  61.           for (i = 1; i <= n; ++i)  
  62.           {  
  63.               for (j = 1; j <= m; ++j)  
  64.               {  
  65.                   if(g[i][j] == true)  
  66.                   {  
  67.                       g[i][j] = false;  
  68.                       if(MaxMatch() < ans)  
  69.                           num++;  
  70.                       g[i][j] = true;  
  71.                   }  
  72.               }  
  73.           }  
  74.           printf("Board %d have %d important blanks for %d chessmen.\n",t++,num,ans);  
  75.     }  
  76.     return 0;  
  77. }  

http://acm.hdu.edu.cn/showproblem.php?pid=2819   Swap

题目的意思是,通过一系列交换,让矩阵中A[i, i] (1 <= i <= N)的值全为1。
首先要明确:如果某行或者某列全是0。那怎么换都没办法的。否则,一定能换出来。这个动动脑子想一下可以明白的。

其实就是简单的二分匹配,行和列匹配就可以了,关键是点不在于匹配而在于排序,因为匹配后的match存储的是列的匹配对象,所以只需要把列从小到大(或者从大到小,因为是special judge,所以主、副对角线都是一样)排序,每排序一次就保存当前交换了的下标,注意这里不能用冒泡而最好用选择,因为题目要求len不能大于1000,,这里纠结了一下,郁闷死了)
其次要明确:只交换行或者只交换列都是可以换出来的,这个动动脑子想一下也可以明白的。

明确了这两点,这问题就变成了二分图的匹配问题。
二分图左边的节点为每一行的行号,二分图右边的节点为每一行中出现的“1”对应的列号,这样用匈牙利算法就可以匹配了。

[cpp]  view plain  copy
  1. /* 
  2. 最大二分匹配+选择排序 
  3. 开始压根没想到会是二分图匹配问题,做题太少,只能说水平不够吧。 
  4. 将行数放在左边,右边为连接该行为1的所在列数,再求二分图的最大匹配,若能完全匹配,则存在。 
  5. 再用选择排序的方法,次算出行之间的交换次数,并保存结果。 
  6. */  
  7. #include<iostream>  
  8. #include<cstdio>  
  9. using namespace std;  
  10. #include<memory.h>  
  11.   
  12. bool visit[101];    //记录V2中的某个点是否被搜索过  
  13. int match[101];   //记录与V2中的点匹配的点的编号  
  14. int n;   //二分图中左边、右边集合中顶点的数目  
  15. bool flag;  
  16. int a[101],b[101],head[101];  
  17.   
  18. struct edge    
  19. {  
  20.     int to,next;    
  21. }e[5005];    
  22. int index;    
  23.     
  24. void addedge(int u,int v)    
  25. {   //向图中加边的算法,注意加上的是有向边    
  26.     //u为v的后续节点既是v---->u    
  27.     e[index].to=v;    
  28.     e[index].next=head[u];    
  29.     head[u]=index;    
  30.     index++;  
  31. }  
  32.   
  33. // 匈牙利(邻接表)算法    
  34. bool dfs(int u)    
  35. {    
  36.     int i,v;  
  37.     for(i = head[u]; i != 0; i = e[i].next)    
  38.     {    
  39.         v = e[i].to;    
  40.         if(!visit[v])   //如果节点v与u相邻并且未被查找过    
  41.         {    
  42.             visit[v] = true;   //标记v为已查找过    
  43.             if(match[v] == -1 || dfs(match[v]))   //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径    
  44.             {    
  45.                 match[v] = u;  //记录查找成功记录,更新匹配M(即“取反”)    
  46.                 return true;   //返回查找成功    
  47.             }  
  48.         }  
  49.     }  
  50.     return false;    
  51. }  
  52.   
  53. int MaxMatch()  
  54. {    
  55.     int i,sum=0;  
  56.     memset(match,-1,sizeof(match));  
  57.     for(i = 1 ; i <= n ; ++i)  
  58.     {  
  59.         memset(visit,false,sizeof(visit));   //清空上次搜索时的标记    
  60.         if( dfs(i) )    //从节点i尝试扩展    
  61.         {  
  62.             sum++;    
  63.         }  
  64.         else  
  65.         {  
  66.             flag = false;  
  67.             break;  
  68.         }  
  69.     }    
  70.     return sum;    
  71. }  
  72. inline bool scan_d(int &num)  //  这个就是 加速的 关键了   
  73. {  
  74.     char in;bool IsN=false;  
  75.     in=getchar();  
  76.     if(in==EOF)  
  77.         return false;  
  78.     while(in!='-'&&(in<'0'||in>'9')) in=getchar();  
  79.     if(in=='-')   { IsN=true;num=0;}  
  80.     else num=in-'0';  
  81.     while(in=getchar(),in>='0'&&in<='9')  
  82.     {  
  83.         num*=10,num+=in-'0';  
  84.     }  
  85.     if(IsN)  
  86.         num=-num;  
  87.     return true;  
  88. }  
  89.   
  90. int main(void)  
  91. {  
  92.     int i,j,ans,k,cnt,value;  
  93.     while (scanf("%d",&n)!=EOF)  
  94.     {  
  95.         index = 1;  
  96.         flag = true;  
  97.         memset(head,0,sizeof(head));    //切记要初始化,否则会超时的    
  98.         for (i = 1; i <= n; i++)  
  99.         {  
  100.             for (j = 1; j <= n; j++)  
  101.             {  
  102.                 scan_d(value);  
  103.                 if(value)  
  104.                     addedge(i,j);  
  105.             }  
  106.         }  
  107.         ans = MaxMatch();  
  108.         if(!flag)  
  109.             puts("-1");  
  110.         else  
  111.         {  
  112.             cnt = 0;  
  113.             for (i = 1; i <= n; i++)  
  114.             {  
  115.                 k = i;  
  116.                 for(j = i; j <= n; j++)  
  117.                 {  
  118.                     if(match[j] < match[k])  
  119.                         k = j;  
  120.                 }  
  121.                 if(k != i)  
  122.                 {  
  123.                     a[cnt]=i;  
  124.                     b[cnt++]=k;  
  125.                     swap(match[i] , match[k]);  
  126.                 }  
  127.             }  
  128.             printf("%d\n",cnt);  
  129.             for (i = 0; i < cnt; i++)  
  130.                 printf("C %d %d\n",a[i],b[i]);  
  131.         }  
  132.     }  
  133.     return 0;  
  134. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值