二分图的最大匹配以及带权匹配【匈牙利算法+KM算法】
二分图算法包括 匈牙利算法 与 KM算法。
匈牙利算法
在这里写上模板。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2063
1 #include<stdio.h> 2 #include<string.h> 3 #define mem(a, b) memset(a, b, sizeof(a)) 4 5 int head[510], cnt; 6 int k, m, n; //k为组合数,m为女生人数,n为男生人数 7 int used[510], master[510]; 8 9 struct Edge 10 { 11 int to, next; 12 }edge[1010]; 13 14 void add(int a, int b) 15 { 16 edge[++ cnt].to = b; 17 edge[cnt].next = head[a]; 18 head[a] = cnt; 19 } 20 21 int find(int x) 22 { 23 for(int i = head[x]; i != -1; i = edge[i].next) 24 { 25 int to = edge[i].to; 26 if(used[to] == -1) 27 { 28 used[to] = 1; 29 if(master[to] == -1 || find(master[to])) 30 { 31 master[to] = x; 32 return 1; 33 } 34 } 35 } 36 return 0; 37 } 38 39 int main() 40 { 41 int ans; 42 while(scanf("%d", &k)!=EOF) 43 { 44 if(k == 0) 45 break; 46 cnt = ans = 0; 47 mem(head, -1), mem(master, -1); 48 scanf("%d%d", &m, &n); 49 for(int i = 1; i <= k; i ++) 50 { 51 int a, b; 52 scanf("%d%d", &a, &b); 53 add(a, b); 54 } 55 for(int i = 1; i <= m; i ++) 56 { 57 mem(used, -1); 58 if(find(i)) 59 ans ++; 60 } 61 printf("%d\n", ans); 62 } 63 return 0; 64 }
KM算法
KM算法是用来解决带权问题的最大匹配. (用邻接矩阵实现, 首先因为带权的话, X部,Y部都有边,一般是稠密图,其次邻接表并不好实现)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2255
1 #include<stdio.h> 2 #include<string.h> 3 #define mem(a, b) memset(a, b, sizeof(a)) 4 const int inf = 0x3f3f3f3f; 5 6 int n, nx, ny; 7 int lx[310], ly[310];//x部点的值,y部点的值 8 int visx[310], visy[310];//标记x,y部的点是否在相等子图中,用于更新点的值 9 int slack[310];//松弛量, 用于优化KM算法 优化后复杂度为 n^3 10 int goal[310], weight[310][310]; 11 12 int find(int x)//新增的x部的点 13 { 14 visx[x] = 1; 15 for(int j = 1; j <= ny; j ++) 16 { 17 if(!visy[j]) 18 { 19 int t = lx[x] + ly[j] - weight[x][j]; //匹配到的标准是 x部 + y部点的值等于边权值 20 if(t == 0) 21 { 22 visy[j] = 1; 23 if(goal[j] == -1 || find(goal[j])) 24 { 25 goal[j] = x; 26 return 1; 27 } 28 } 29 else if(slack[j] > t)//没被匹配到的点记录最小slack 30 slack[j] = t; 31 } 32 } 33 return 0; 34 } 35 36 int km() 37 { 38 mem(ly, 0); //y部的初始化为0 39 mem(lx, 0); 40 mem(goal, -1); 41 for(int i = 1; i <= nx; i ++)//x部点的值初始化为与y部相连的最大值 42 for(int j = 1; j <= ny; j ++) 43 if(weight[i][j] > lx[i]) 44 lx[i] = weight[i][j]; 45 for(int i = 1; i <= nx; i ++) 46 {//每次扩充一个点, 都要重新初始化y部的slack,因为需要在相等子图中找到最大的权值匹配 47 for(int j = 1; j <= ny; j ++) 48 slack[j] = inf; 49 while(1) 50 { 51 mem(visx, 0); 52 mem(visy, 0); 53 if(find(i)) //如果当前子图可以匹配的到就跳出, 扩充下一个x部的点继续匹配 54 break; 55 //如果当前子图没匹配到,就用slack更新值再循环while寻找当前子图的最大权值匹配 56 int d = inf; 57 for(int j = 1; j <= ny; j ++) 58 if(!visy[j] && d > slack[j]) 59 d = slack[j];//找到一个最小的差值 在未尝试匹配的y部中找 60 for(int j = 1; j <= ny; j ++) 61 if(!visy[j]) 62 slack[j] -= d; 63 for(int j = 1; j <= n; j ++)//参与匹配的点x部的减 ,y部的加 64 { 65 if(visy[j]) 66 ly[j] += d; 67 if(visx[j]) 68 lx[j] -= d; 69 } 70 } 71 } 72 int ans = 0; 73 for(int j = 1; j <= ny; j ++) 74 if(goal[j] != -1) 75 ans += weight[goal[j]][j]; 76 return ans; 77 } 78 79 int main() 80 { 81 while(scanf("%d", &n)!=EOF) 82 { 83 nx = n, ny = n; 84 for(int i = 1; i <= n; i ++) 85 for(int j = 1; j <= n; j ++) 86 scanf("%d", &weight[i][j]); 87 int ans = km(); 88 printf("%d\n", ans); 89 } 90 return 0; 91 }
对于KM算法求最小匹配, 只需要在最大匹配的模板上改动几个地方即可,
在存图时将边权全记为负边权,那么会发现在对lx顶标记录最大值的时候实际上是绝对值最小的负值,也就是最小匹配了. 将lx[]数组初始化为-inf,然后对于最后的答案取负号就可以了.