KM算法是用来求完备匹配下的最大权匹配: 在一个二分图内,左顶点为X,右顶点为Y,现对于每组左右连接<Xi,Yj>有权Wij,求一种匹配使得所有Wij的和最大-------即最佳匹配。
记 L(x) 表示结点 x 的标记量,如果对于二部图中的任何边<x,y>,都有 L(x)+ L(y)>= Wx,y,我们称 L 为二部图的可行顶标。
KM算法及其具体过程
对于以上KM算法的大致步骤,我们在详细叙述它的具体步骤:
① 初始化可行顶标:
x[i]=max{e.W|e.x=i};即每个X方点的初始标号为与这个X方点相关联的权值最大的边的权值。
ly[j]=0;即每个Y方点的初始标号为0。
这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边;
② 从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方的点全部记下来进行后面的修改;
③ 若增广成功,则从下一点继续增广。
④ 得到 d 值,修改可行线标后继续增广。
以上就是整个算法的全部步骤。
对于第二步,增广的结果有两种:若成功,则该点增广完成,进入下一个点的增广。若失败,则需要改变一些点的标号,使得图中可行边的数量增加。
方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,所有在增广轨中的Y方点的标号全部加上一个常数d。
则对于图中的任意一条边(i, j, W)(i为X方点,j为Y方点):
<1>i和j都在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变(原来是可行边则现在仍是,原来不是则现在仍不是);
<2>i在增广轨中而j不在:此时边(i, j)的(lx[i]+ly[j])的值减少了d,也就是原来这条边不是可行边(否则j就会被遍历到了),而现在可能是;
<3>j在增广轨中而i不在:此时边(i, j)的(lx[i]+ly[j])的值增加了d,也就是原来这条边不是可行边(若这条边是可行边,则在遍历到j时会紧接着执行DFS(i),此时i就会被遍历到),现在仍不是;
<4>i和j都不在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变。
这样,在进行了这一步修改操作后,图中原来的可行边仍可行,而原来不可行的边现在则可能变为可行边。那么d的值应取多少?
显然,整个点标不能失去可行性,也就是对于上述的第<2>类边,其lx[i]+ly[j]>=W这一性质不能被改变,故取所有第<2>类边的(lx[i]+ly[j]-W)的最小值作为d值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。
算法改进
以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶标,每次修改顶标时由于要枚举边来求d值,复杂度为O(n2)。实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数slack,每次开始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]n等于原值与 A[i]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d。
【求二分图的最小权匹配】
只需把权值取反,变为负的,再用KM算出最大权匹配,取反则为其最小权匹配。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define _Clr(x, y) memset(x, y, sizeof(x)) 5 #define INF 0x3f3f3f3f 6 #define N 310 7 using namespace std; 8 9 int map[N][N], match[N]; 10 int lx[N], ly[N]; 11 int slack[N], n; 12 bool used_x[N], used_y[N]; 13 14 bool dfs(int x) 15 { 16 used_x[x] = true; 17 for(int i=1; i<=n; i++) 18 { 19 if(used_y[i]) continue; 20 int tp = lx[x]+ly[i]-map[x][i]; 21 if(tp==0) // 在相等子图中:<x, i>为可行边 22 { 23 used_y[i] = true; 24 if(match[i]==-1 || dfs(match[i])) 25 { 26 match[i] = x; 27 return true; 28 } 29 } 30 else 31 slack[i] = min(slack[i], tp); 32 } 33 return false; 34 } 35 36 int KM() 37 { 38 // 初始化可行项标 39 _Clr(ly, 0); 40 _Clr(match, -1); 41 for(int i=1, j=1; i<=n; i++) 42 for(j=1, lx[i]=-INF; j<=n; j++) 43 lx[i] = max(lx[i], map[i][j]); 44 45 for(int i=1; i<=n; i++) 46 { 47 _Clr(slack, INF); // 初始化Y方点的松弛量函数 48 while(1) 49 { 50 _Clr(used_x, 0); 51 _Clr(used_y, 0); 52 if(dfs(i)) break; // 找到增广路,找下一条。 53 54 // 获取d值:不在增广轨中的Y方的松弛量slack[]的最小值 55 int d = INF; 56 for(int j=1; j<=n; j++) 57 if(!used_y[j] && slack[j] < d) 58 d = slack[j]; 59 60 // 在增广轨中的 X 方点的lx[]减去d值 61 for(int j=1; j<=n; j++) 62 if(used_x[j]) lx[j] -= d; 63 64 // 在增广轨中的 Y 方点的ly[]加上d值, 65 // 同时不再增广轨中的 Y 方点松弛量减去d值 66 for(int j=1; j<=n; j++) 67 if(!used_y[j]) 68 slack[j] -= d; 69 else ly[j] += d; 70 } 71 } 72 int ans=0; 73 for(int i=1; i<=n; i++) 74 if(match[i] > -1) ans += map[match[i]][i]; 75 return ans; 76 } 77 78 int main() 79 { 80 while(~scanf("%d", &n)) 81 { 82 for(int i=1; i<=n; i++) 83 for(int j=1; j<=n; j++) 84 scanf("%d", &map[i][j]); 85 printf("%d ", KM()); 86 } 87 return 0; 88 }
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define _Clr(x, y) memset(x, y, sizeof(x)) 5 #define INF 0xfffffff 6 #define N 520 7 using namespace std; 8 9 int mat[N][N], match[N]; 10 int slack[N], n, m; 11 int lx[N], ly[N]; 12 bool used_x[N], used_y[N]; 13 14 bool dfs(int x) 15 { 16 used_x[x] = true; 17 for(int i=0; i<m; i++) 18 { 19 if(used_y[i]) continue; 20 int t = lx[x] + ly[i] - mat[x][i]; 21 if(t==0) 22 { 23 used_y[i] = true; 24 if(match[i]==-1 || dfs(match[i])) 25 { 26 match[i] = x; 27 return true; 28 } 29 } 30 else slack[i] = min(slack[i], t); 31 } 32 return false; 33 } 34 35 void KM() 36 { 37 _Clr(ly, 0); 38 _Clr(match, -1); 39 for(int i=0, j; i<n; i++) 40 for(lx[i]=-INF,j=0; j<m; j++) 41 lx[i] = max(lx[i], mat[i][j]); 42 for(int x=0; x<n; x++) 43 { 44 for(int i=0; i<m; i++) slack[i] = INF; 45 while(1) 46 { 47 _Clr(used_x, 0); 48 _Clr(used_y, 0); 49 if(dfs(x)) break; 50 51 int d = INF; 52 for(int i=0; i<m; i++) 53 if(!used_y[i] && slack[i] < d) 54 d = slack[i]; 55 if(d==INF) 56 { 57 puts("-1"); 58 return; 59 } 60 for(int i=0; i<n; i++) 61 if(used_x[i]) lx[i] -= d; 62 for(int i=0; i<m; i++) 63 if(used_y[i]) ly[i] += d; 64 else slack[i] -= d; 65 } 66 } 67 int cnt=0, ans=0; 68 for(int i=0;i<m; i++) 69 if(match[i]!=-1 && mat[match[i]][i]!=-INF) 70 ans += mat[match[i]][i], cnt++; 71 if(cnt==n) 72 printf("%d ", ans); 73 else puts("-1"); 74 } 75 int main() 76 { 77 int s, r, v, k; 78 int lop=0; 79 while(~scanf("%d%d%d", &n, &m, &k)) 80 { 81 for(int i=0; i<n; i++) 82 for(int j=0; j<m; j++) mat[i][j] = -INF; 83 for(int i=0; i<k; i++) 84 { 85 scanf("%d%d%d", &s, &r, &v); 86 if(v >= 0) 87 mat[s][r] = v; 88 } 89 printf("Case %d: ", ++lop); 90 if(n>m) 91 puts("-1"); 92 else 93 KM(); 94 } 95 return 0; 96 }