二分图的最大匹配以及带权匹配【匈牙利算法+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 }
View Code

 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 }
View Code

对于KM算法求最小匹配, 只需要在最大匹配的模板上改动几个地方即可,

在存图时将边权全记为负边权,那么会发现在对lx顶标记录最大值的时候实际上是绝对值最小的负值,也就是最小匹配了. 将lx[]数组初始化为-inf,然后对于最后的答案取负号就可以了.

posted @ 2019-05-03 16:11  缘未到  阅读(1191)  评论(0编辑  收藏  举报