KM算法——带权二分图最佳匹配问题
来源:互联网 发布:最大公约数c语言算法 编辑:程序博客网 时间:2024/06/05 14:37
【参考】 我对KM算法的理解
(1) 初始化可行标杆
(2) 用匈牙利算法寻找完备匹配
(3) 若未找到完备匹配则修改可行标杆
(4) 重复(2)(3)直到找到相等子图的完备匹配
关于该算法的流程及实施,网上有很多介绍,基本上都是围绕可行标杆如何修改而进行的讨论,至于原理并没有给出深入的探讨。
KM算法是用于寻找带权二分图最佳匹配的算法。
二分图是这样一种图:所有顶点可以分成两个集:X和Y,其中X和Y中的任意两个在同一个集中的点都不相连,而来自X集的顶点与来自Y集的顶点有连线。当这些连线被赋于一定的权重时,这样的二分图便是带权二分图。
二分图匹配是指求出一组边,其中的顶点分别在两个集合中,且任意两条边都没有相同的顶点,这组边叫做二分图的匹配,而所能得到的最大的边的个数,叫做二分图的最大匹配。
我们也可以换个角度看二分图的最大匹配,即二分图的每条边的默认权重为1,我们求到的二分图的最大匹配的权重最大。对于带权二分图,其边有大于0的权重,找到一组匹配,使其权重最大,即为带权二分图的最佳匹配。
匈牙利算法一般用于寻找二分图的最大匹配。算法根据一定的规则选择二分图的边加入匹配子图中,其基本模式为:
初始化匹配子图为空
While 找得到增广路径
Do 把增广路径添加到匹配子图中
增广路径有如下特性:
1. 有奇数条边
2. 起点在二分图的X边,终点在二分图的Y边
3. 路径上的点一定是一个在X边,一个在Y边,交错出现。
4. 整条路径上没有重复的点
5. 起点和终点都是目前还没有配对的点,其他的点都已经出现在匹配子图中
6. 路径上的所有第奇数条边都是目前还没有进入目前的匹配子图的边,而所有第偶数条边都已经进入目前的匹配子图。奇数边比偶数边多一条边
7. 于是当我们把所有第奇数条边都加到匹配子图并把条偶数条边都删除,匹配数增加了1.
例如下图,蓝色的是当前的匹配子图,目前只有边x0y0,然后通过x1找到了增广路径:x1y0->y0x0->x0y2
其中第奇数第边x1y0和x0y2不在当前的匹配子图中,而第偶数条边x0y0在匹配子图中,通过添加x1y0和x0y2到匹配子图并删除x0y0,使得匹配数由1增加到了2。每找到一条增广路径,通过添加删除边,我们总是能使匹配数加1.
增广路径有两种寻径方法,一个是深搜,一个是宽搜。例如从x2出发寻找增广路径,如果是深搜,x2找到y0匹配,但发现y0已经被x1匹配了,于是就深入到x1,去为x1找新的匹配节点,结果发现x1没有其他的匹配节点,于是匹配失败,x2接着找y1,发现y1可以匹配,于是就找到了新的增广路径。如果是宽搜,x1找到y0节点的时候,由于不能马上得到一个合法的匹配,于是将它做为候选项放入队列中,并接着找y1,由于y1已经匹配,于是匹配成功返回了。相对来说,深搜要容易理解些,其栈可以由递归过程来维护,而宽搜则需要自己维护一个队列,并对一路过来的路线自己做标记,实现起来比较麻烦。
对于带权重的二分图来说,我们可以把它看成一个所有X集合的顶点到所有Y集合的顶点均有边的二分图(把原来没有的边添加入二分图,权重为0即可),也就是说它必定存在完备匹配(即其匹配数为min(|X|,|Y|))。为了使权重达到最大,我们实际上是通过贪心算法来选边,形成一个新的二分图(我们下面叫它二分子图好了),并在该二分图的基础上寻找最大匹配,当该最大匹配为完备匹配时,我们可以确定该匹配为最佳匹配。(在这里我们如此定义最大匹配:匹配边数最多的匹配和最佳匹配:匹配边的权重和最大的匹配。)
贪心算法总是将最优的边优先加入二分子图,该最优的边将对当前的匹配子图带来最大的贡献,贡献的衡量是通过标杆来实现的。下面我们将通过一个实例来解释这个过程。
有带权二分图:
算法把权重转换成标杆,X集跟Y集的每个顶点各有一个标杆值,初始情况下权重全部放在X集上。由于每个顶点都将至少会有一个匹配点,贪心算法必然优先选择该顶点上权重最大的边(最理想的情况下,这些边正好没有交点,于是我们自然得到了最佳匹配)。最初的二分子图为:(可以看到初始化时X标杆为该顶点上的最大权重,而Y标杆为0)
从X0找增广路径,找到X0Y4;从X1找不到增广路径,也就是说,必须往二分子图里边添加新的边,使得X1能找到它的匹配,同时使权重总和添加最大。由于X1通往Y4而Y4已经被X0匹配,所以有两种可能,一个是为X0找一个新的匹配点并把Y4让给X1,或者是为X1找一个新的匹配点,现在我们将要看到标杆的作用了。根据传统的算法描述,能够进入二分子图的边的条件为L(x)+L(y)>=weight(xy)。当找不到增广路径时,对于搜索过的路径上的XY点,设该路径上的X顶点集为S,Y顶点集为T,对所有在S中的点xi及不在T中的点yj,计算d=min{(L(xi)+L(yj)-weight(xiyj))},从S集中的X标杆中减去d,并将其加入到T集中的Y的标杆中,由于S集中的X标杆减少了,而不在T中的Y标杆不变,相当于这两个集合中的L(x)+L(y)变小了,也就是,有新的边可以加入二分子图了。从贪心选边的角度看,我们可以为X0选择新的边而抛弃原先的二分子图中的匹配边,也可以为X1选择新的边而抛弃原先的二分子图中的匹配边,因为我们不能同时选择X0Y4和X1Y4,因为这是一个不合法匹配,这个时候,d=min{(L(xi)+L(yj)-weight(xiyj))}的意义就在于,我们选择一条新的边,这条边将被加入匹配子图中使得匹配合法,选择这条边形成的匹配子图,将比原先的匹配子图加上这条非法边组成的非法匹配子图的权重和(如果它是合法的,它将是最大的)小最少,即权重最大了。好绕口的。用数学的方式表达,设原先的不合法匹配(它的权重最大,因为我们总是从权重最大的边找起的)的权重为W,新的合法匹配为W’,d为min{W-W’i}。在这个例子中,S={X0, X1},Y={Y4},求出最小值d=L(X1)+L(Y0)-weight(X1Y0)=2,得到新的二分子图:
重新为X1寻找增广路径,找到X1Y0,可以看到新的匹配子图的权重为9+6=15,比原先的不合法的匹配的权重9+8=17正好少d=2。
接下来从X2出发找不到增广路径,其走过的路径如蓝色的路线所示。形成的非法匹配子图:X0Y4,X1Y0及X2Y0的权重和为22。在这条路径上,只要为S={X0,X1,X2}中的任意一个顶点找到新的匹配,就可以解决这个问题,于是又开始求d。
d=L(X0)+L(Y2)-weight(X0Y2)=L(X2)+L(Y1)-weight(X2Y1)=1.
新的二分子图为:
重新为X2寻找增广路径,如果我们使用的是深搜,会得到路径:X2Y0->Y0X1->X1Y4->Y4X0->X0Y2,即奇数条边而删除偶数条边,新的匹配子图中由这几个顶点得到的新的权重为21;如果使用的是宽搜,会得到路径X2Y1,另上原先的两条匹配边,权重为21。假设我们使用的是宽搜,得到的新的匹配子图为:
接下来依次类推,直到为X4找到一个匹配点。
KM算法的最大特点在于利用标杆和权重来生成一个二分子图,在该二分子图上面找最大匹配,而且,当些仅当找到完备匹配,才能得到最佳匹配。标杆和权重的作用在于限制新边的加入,使得加入的新边总是能为子图添加匹配数,同时又令权重和得到最大的提高。
- //---------------------DFS---------------------------------
- #include<iostream>
- #include<memory.h>
- using namespace std;
- #define MAXN 10
- int graph[MAXN][MAXN];
- int match[MAXN];
- int visitX[MAXN], visitY[MAXN];
- int nx, ny;
- bool findPath( int u )
- {
- visitX[u] = 1;
- for( int v=0; v<ny; v++ )
- {
- if( !visitY[v] && graph[u][v] )
- {
- visitY[v] = 1;
- if( match[v] == -1 || findPath(match[v]) )
- {
- match[v] = u;
- return true;
- }
- }
- }
- return false;
- }
- int dfsHungarian()
- {
- int res = 0;
- memset( match, -1, sizeof(match) );
- for( int i=0; i<nx; i++ )
- {
- memset( visitX, 0, sizeof(visitX) );
- memset( visitY, 0, sizeof(visitY) );
- if( findPath(i) )
- res++;
- }
- return res;
- }
- //-----------------------------BFS-------------------------------
- #include<iostream>
- #include<memory.h>
- using namespace std;
- #define MAXN 10
- int graph[MAXN][MAXN];
- //在bfs中,增广路径的搜索是一层一层展开的,所以必须通过prevX来记录上一层的顶点
- //chkY用于标记某个Y顶点是否被目前的X顶点访问尝试过。
- int matchX[MAXN], matchY[MAXN], prevX[MAXN], chkY[MAXN];
- int queue[MAXN];
- int nx, ny;
- int bfsHungarian()
- {
- int res = 0;
- int qs, qe;
- memset( matchX, -1, sizeof(matchX) );
- memset( matchY, -1, sizeof(matchY) );
- memset( chkY, -1, sizeof(chkY) );
- for( int i=0; i<nx; i++ )
- {
- if( matchX[i] == -1 ) //如果该X顶点未找到匹配点,将其放入队列。
- {
- qs = qe = 0;
- queue[qe++] = i;
- prevX[i] = -1; //并且标记,它是路径的起点
- bool flag = 0;
- while( qs<qe && !flag )
- {
- int u = queue[qs];
- for( int v=0; v<ny&&!flag; v++ )
- {
- if( graph[u][v] && chkY[v]!=i ) //如果该节点与u有边且未被访问过
- {
- chkY[v] = i; //标记且将它的前一个顶点放入队列中,也就是下次可能尝试这个顶点看能否为它找到新的节点
- queue[qe++] = matchY[v];
- if( matchY[v] >= 0 )
- prevX[matchY[v]] = u;
- else //到达了增广路径的最后一站
- {
- flag = 1;
- int d=u, e=v;
- while( d!=-1 ) //一路通过prevX找到路径的起点
- {
- int t = matchX[d];
- matchX[d] = e;
- matchY[e] = d;
- d = prevX[d];
- e = t;
- }
- }
- }
- }
- qs++;
- }
- if( matchX[i] != -1 )
- res++;
- }
- }
- return res;
- }
模板:(O ^ 4)
#define M 505
#define inf 0x3fffffff
bool sx[M], sy[M];
int match[M], w[M][M], n, m, d, lx[M], ly[M];
//n:左集元素个数; m:右集元素个数
void init ()
{
memset (w, 0, sizeof(w)); //不一定要,求最小值一般要初始化为负无穷!
}
bool dfs (int u)
{
int v; sx[u] = true;
for (v = 0; v < m; v++)
{
if (!sy[v] && lx[u]+ly[v]==w[u][v])
{
sy[v] = true;
if (match[v] == -1 || dfs (match[v]))
{
match[v] = u;
return true;
}
}
}
return false;
}
int KM ()
{
int i, j, k, sum = 0;
memset (ly, 0, sizeof(ly));
for (i = 0; i < n; i++)
{
lx[i] = -inf;
for (j = 0; j < m; j++)
if (lx[i] < w[i][j])
lx[i] = w[i][j];
}
memset (match, -1, sizeof(match));
for (i = 0; i < n; i++)
{
while (1)
{
memset (sx, false, sizeof(sx));
memset (sy, false, sizeof(sy));
if (dfs (i))
break;
d = inf;
for (j = 0; j < n; j++)
if (sx[j])
for (k = 0; k < m; k++)
if (!sy[k])
d = min (d, lx[j]+ly[k]-w[j][k]);
if (d == inf) //找不到完美匹配
return -1;
for (j = 0; j < n; j++)
if (sx[j])
lx[j] -= d;
for (j = 0; j < m; j++)
if (sy[j])
ly[j] += d;
}
}
for (i = 0; i < m; i++)
if (match[i] > -1)
sum += w[match[i]][i];
return sum;
}
改进后的模板(O^3)
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*/
/*最大最小有一些地方不同。。*/
#include <iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
//赤裸裸的模板啊。。
const int maxn = 101;
const int INF = (1<<31)-1;
int w[maxn][maxn];
int lx[maxn],ly[maxn]; //顶标
int linky[maxn];
int visx[maxn],visy[maxn];
int slack[maxn];
int nx,ny;
bool find(int x)
{
visx[x] = true;
for(int y = 0; y < ny; y++)
{
if(visy[y])
continue;
int t = lx[x] + ly[y] - w[x][y];
if(t==0)
{
visy[y] = true;
if(linky[y]==-1 || find(linky[y]))
{
linky[y] = x;
return true; //找到增广轨
}
}
else if(slack[y] > t)
slack[y] = t;
}
return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)
}
int KM() //返回最优匹配的值
{
int i,j;
memset(linky,-1,sizeof(linky));
memset(ly,0,sizeof(ly));
for(i = 0; i < nx; i++)
for(j = 0,lx[i] = -INF; j < ny; j++)
if(w[i][j] > lx[i])
lx[i] = w[i][j];
for(int x = 0; x < nx; x++)
{
for(i = 0; i < ny; i++)
slack[i] = INF;
while(true)
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(find(x)) //找到增广轨,退出
break;
int d = INF;
for(i = 0; i < ny; i++) //没找到,对l做调整(这会增加相等子图的边),重新找
{
if(!visy[i] && d > slack[i])
d = slack[i];
}
for(i = 0; i < nx; i++)
{
if(visx[i])
lx[i] -= d;
}
for(i = 0; i < ny; i++)
{
if(visy[i])
ly[i] += d;
else
slack[i] -= d;
}
}
}
int result = 0;
for(i = 0; i < ny; i++)
if(linky[i]>-1)
result += w[linky[i]][i];
return result;
}
int main()
{
// freopen("g:/1.txt","r",stdin);
while(true)
{
scanf("%d%d",&nx,&ny);
int a,b,c;
while(scanf("%d%d%d",&a,&b,&c),a+b+c)
{
w[a][b]=c;
}
printf("%d\n",KM());
break;
}
return 0;
}
【KM算法及其具体过程】
(1)可行点标:每个点有一个标号,记lx[i]为X方点i的标号,ly[j]为Y方点j的标号。如果对于图中的任意边(i, j, W)都有lx[i]+ly[j]>=W,则这一组点标是可行的。特别地,对于lx[i]+ly[j]=W的边(i, j, W),称为可行边;
(2)KM 算法的核心思想就是通过修改某些点的标号(但要满足点标始终是可行的),不断增加图中的可行边总数,直到图中存在仅由可行边组成的完全匹配为止,此时这个 匹配一定是最佳的(因为由可行点标的的定义,图中的任意一个完全匹配,其边权总和均不大于所有点的标号之和,而仅由可行边组成的完全匹配的边权总和等于所 有点的标号之和,故这个匹配是最佳的)。一开始,求出每个点的初始标号:lx[i]=max{e.W|e.x=i}(即每个X方点的初始标号为与这个X方 点相关联的权值最大的边的权值),ly[j]=0(即每个Y方点的初始标号为0)。这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边;
(3)然后,从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方点全部记下来(可以用vst搞一下),以进行后面的修改;
(4) 增广的结果有两种:若成功(找到了增广轨),则该点增广完成,进入下一个点的增广。若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的 数量增加。方法为:将所有在增广轨中(就是在增广过程中遍历到)的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值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。
(5)修改后,继续对这个X方点DFS增广,若还失败则继续修改,直到成功为止;
(6)以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶标,每次修改顶 标时由于要枚举边来求d值,复杂度为O(n2)。实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数slack,每次开 始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与 A[i]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修 改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d。
【求二分图的最小匹配】
只需把权值取反,变为负的,再用KM算出最大权匹配,取反则为其最小权匹配。
题目推荐:
第一题:hdu 2255 奔小康赚大钱
http://acm.hdu.edu.cn/showproblem.php?pid=2255
模板不解释
复制代码/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>//赤裸裸的模板啊。。const int maxn = 301;const int INF = (1<<31)-1;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];int n;bool find(int x){ visx[x] = true; for(int y = 1; y <=n; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t==0) { visy[y] = true; if(linky[y]==-1 || find(linky[y])) { linky[y] = x; return true; //找到增广轨 } } else if(slack[y] > t) slack[y] = t; } return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM() //返回最优匹配的值{ int i,j; memset(linky,-1,sizeof(linky)); memset(ly,0,sizeof(ly)); for(i = 1; i <=n; i++) { lx[i] = -INF; for(j = 1; j <=n; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; } for(int x = 1; x <=n; x++) { for(i = 1; i <=n; i++) slack[i] = INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(find(x)) //找到增广轨,退出 break; int d = INF; for(i = 1; i <=n; i++) //没找到,对l做调整(这会增加相等子图的边),重新找 { if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 1; i <=n; i++) { if(visx[i]) lx[i] -= d; } for(i = 1; i <=n; i++) { if(visy[i]) ly[i] += d; else slack[i] -= d; } } } int result = 0; for(i = 1; i <=n; i++) if(linky[i]>-1) result += w[linky[i]][i]; return result;}int main(){ // freopen("g:/1.txt","r",stdin); while(scanf("%d",&n)==1) { int cost; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { scanf("%d",&cost); w[i][j]=cost; } printf("%d\n",KM()); } return 0;}复制代码
第二题: hdu 1533 Going Home
http://acm.hdu.edu.cn/showproblem.php?pid=1533
用 w[i][j] = -w[i][j]建图再套模板 求最大值 输出【-sum】
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int fx,fy;const int maxn = 301;const int INF = 0xffffff;struct node{ int x,y;}sx[maxn],sy[maxn];int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){ visx[x] = true; for(int y = 1; y <=fy; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t==0) { visy[y] = true; if(linky[y]==-1 || find(linky[y])) { linky[y] = x; return true; //找到增广轨 } } else if(slack[y] > t) slack[y] = t; } return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM() //返回最优匹配的值{ int i,j; memset(linky,-1,sizeof(linky)); memset(ly,0,sizeof(ly)); for(i = 1; i <=fx; i++) { lx[i] = -INF; for(j = 1; j <=fy; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; } for(int x = 1; x <=fx; x++) { for(i = 1; i <=fy; i++) slack[i] = INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(find(x)) //找到增广轨,退出 break; int d = INF; for(i = 1; i <=fy; i++) //没找到,对l做调整(这会增加相等子图的边),重新找 { if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 1; i <=fx; i++) { if(visx[i]) lx[i] -= d; } for(i = 1; i <=fy; i++) { if(visy[i]) ly[i] += d; else slack[i] -= d; } } } int result = 0; for(i = 1; i <=fy; i++) if(linky[i]>-1) result += w[linky[i]][i]; return result;}int main(){ // freopen("g:/1.txt","r",stdin); int n,m; while(scanf("%d%d",&n,&m),n+m) { char cost[maxn]; int i,j;fx=0,fy=0; for( i=0;i<n;i++) { scanf("%s",cost); for(int j=0;j<m;j++) if(cost[j]=='m') sx[++fx].x=i,sx[fx].y=j; else if(cost[j]=='H') sy[++fy].x=i,sy[fy].y=j; } for(i=1;i<=fx;i++) { for(j=1;j<=fy;j++) { int xx=fabs(sx[i].y-sy[j].y)+fabs(sx[i].x-sy[j].x); w[i][j]=-xx; } } printf("%d\n",-KM()); } return 0;}
第三题: hdu 1853 Cyclic Tour
注意是有向图,和重边的判断
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m;const int maxn = 105;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){ visx[x] = true; for(int y = 1; y <=n; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t==0) { visy[y] = true; if(linky[y]==-1 || find(linky[y])) { linky[y] = x; return true; //找到增广轨 } } else if(slack[y] > t) slack[y] = t; } return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM() //返回最优匹配的值{ int i,j; memset(linky,-1,sizeof(linky)); memset(ly,0,sizeof(ly)); for(i = 1; i <=n; i++) { lx[i] = -INF; for(j = 1; j <=n; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; } for(int x = 1; x <=n; x++) { for(i = 1; i <=n; i++) slack[i] = INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(find(x)) //找到增广轨,退出 break; int d = INF; for(i = 1; i <=n; i++) //没找到,对l做调整(这会增加相等子图的边),重新找 { if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 1; i <=n; i++) { if(visx[i]) lx[i] -= d; } for(i = 1; i <=n; i++) { if(visy[i]) ly[i] += d; else slack[i] -= d; } } } int result = 0 ; for(i = 1; i <=n; i++) { if(linky[i]==-1||w[linky[i]][i]==-INF) return 1; else result += w[linky[i]][i]; } return result;}int main(){ // freopen("g:/1.txt","r",stdin); while(scanf("%d%d",&n,&m)==2) { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) w[i][j]=-INF; } for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); if(-c>w[a][b]) w[a][b]=-c; } printf("%d\n",-KM()); } return 0;}
第四题:hdu 3488 Tour
http://acm.hdu.edu.cn/showproblem.php?pid=3488
跟第三题几乎一样
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m;const int maxn = 205;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){ visx[x] = true; for(int y = 1; y <=n; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t==0) { visy[y] = true; if(linky[y]==-1 || find(linky[y])) { linky[y] = x; return true; //找到增广轨 } } else if(slack[y] > t) slack[y] = t; } return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM() //返回最优匹配的值{ int i,j; memset(linky,-1,sizeof(linky)); memset(ly,0,sizeof(ly)); for(i = 1; i <=n; i++) { lx[i] = -INF; for(j = 1; j <=n; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; } for(int x = 1; x <=n; x++) { for(i = 1; i <=n; i++) slack[i] = INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(find(x)) //找到增广轨,退出 break; int d = INF; for(i = 1; i <=n; i++) //没找到,对l做调整(这会增加相等子图的边),重新找 { if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 1; i <=n; i++) { if(visx[i]) lx[i] -= d; } for(i = 1; i <=n; i++) { if(visy[i]) ly[i] += d; else slack[i] -= d; } } } int result = 0 ; for(i = 1; i <=n; i++) { if(linky[i]==-1||w[linky[i]][i]==-INF) return 1; else result += w[linky[i]][i]; } return result;}int main(){ // freopen("g:/1.txt","r",stdin); int cas; scanf("%d",&cas); while(cas--) { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) w[i][j]=-INF; } for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); if(-c>w[a][b]) w[a][b]=-c; } printf("%d\n",-KM()); } return 0;}
第五题:hdu 3435 A new Graph Game
http://acm.hdu.edu.cn/showproblem.php?pid=3435
跟第三题代码基本上一样,只是要双向建图,也有重边
福大 AekdyCoin 出的题,好险的题啊,时间跑了 2000+ ;
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m;const int maxn = 1005;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){ visx[x] = true; for(int y = 1; y <=n; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t==0) { visy[y] = true; if(linky[y]==-1 || find(linky[y])) { linky[y] = x; return true; //找到增广轨 } } else if(slack[y] > t) slack[y] = t; } return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM() //返回最优匹配的值{ int i,j; memset(linky,-1,sizeof(linky)); memset(ly,0,sizeof(ly)); for(i = 1; i <=n; i++) { lx[i] = -INF; for(j = 1; j <=n; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; } for(int x = 1; x <=n; x++) { for(i = 1; i <=n; i++) slack[i] = INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(find(x)) //找到增广轨,退出 break; int d = INF; for(i = 1; i <=n; i++) //没找到,对l做调整(这会增加相等子图的边),重新找 { if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 1; i <=n; i++) { if(visx[i]) lx[i] -= d; } for(i = 1; i <=n; i++) { if(visy[i]) ly[i] += d; else slack[i] -= d; } } }}int main(){ // freopen("g:/1.txt","r",stdin); int cas,p=1; scanf("%d",&cas); while(p<=cas) { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) w[i][j]=-INF; } for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); if(-c>w[a][b]) w[a][b]=w[b][a]=-c; } KM(); int result = 0 ,flag=0; for(int i = 1; i <=n; i++) { if(linky[i]==-1||w[linky[i]][i]==-INF) { flag=1;break; } else result += w[linky[i]][i]; } if(flag) printf("Case %d: NO\n",p++); else printf("Case %d: %d\n",p++,-result); } return 0;}
第六题: hdu 2426 Interesting Housing Problem
http://acm.hdu.edu.cn/showproblem.php?pid=2426
注意 题目输入 |Vi| <= 10000
左集是学生,右集是房子,w[i][j] < 0 不可匹配,最后无法完美匹配输出-1
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m,v;const int maxn = 1005;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){ visx[x] = true; for(int y = 0; y < m; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t==0) { visy[y] = true; if(linky[y]==-1 || find(linky[y])) { linky[y] = x; return true; //找到增广轨 } } else if(slack[y] > t) slack[y] = t; } return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM() //返回最优匹配的值{ int i,j; memset(linky,-1,sizeof(linky)); memset(ly,0,sizeof(ly)); for(i = 0; i <n; i++) { lx[i] = -INF; for(j = 0; j <m; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; } for(int x = 0; x <n; x++) { for(i = 0; i < m; i++) slack[i] = INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(find(x)) //找到增广轨,退出 break; int d = INF; for(i = 0; i < m; i++) //没找到,对l做调整(这会增加相等子图的边),重新找 { if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 0; i < n; i++) { if(visx[i]) lx[i] -= d; } for(i = 0 ; i < m; i++) { if(visy[i]) ly[i] += d; else slack[i] -= d; } } }}int main(){ // freopen("g:/1.txt","r",stdin); int p=1; while(scanf("%d%d%d",&n,&m,&v)==3) { for(int i=0;i<n;i++) { for(int j=0;j<m;j++) w[i][j]=-INF; } for(int i=0;i<v;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); if(c < 0) continue ; w[a][b]=c; } if(v<=0) { printf("Case %d: -1\n",p++);continue;} KM(); int result = 0 ,flag=0; for(int i = 0; i < m; i++) { if(linky[i]>-1&&w[linky[i]][i]!=INF) { result += w[linky[i]][i]; flag++;} } if(flag<n) result=-1; printf("Case %d: %d\n",p++,result); } return 0;}
第七题:hdu 2853 Assignment
http://acm.hdu.edu.cn/showproblem.php?pid=2853
思路:让原有匹配更有优势就可以了
实现:所有权值扩大100倍,原有匹配【例如a匹配b】w[a][b]+ +
设结果是res
最大值:res/100
至少改变个数:n - res%100
这种处理比较有意思
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m,v;const int maxn = 1005;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){ visx[x] = true; for(int y = 1; y <= m; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t==0) { visy[y] = true; if(linky[y]==-1 || find(linky[y])) { linky[y] = x; return true; //找到增广轨 } } else if(slack[y] > t) slack[y] = t; } return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}void KM() //返回最优匹配的值{ int i,j; memset(linky,-1,sizeof(linky)); memset(ly,0,sizeof(ly)); for(i = 1; i <= n; i++) { lx[i] = -INF; for(j = 1; j <=m; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; } for(int x = 1; x <=n; x++) { for(i = 1; i <= m; i++) slack[i] = INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(find(x)) //找到增广轨,退出 break; int d = INF; for(i = 1; i <= m; i++) //没找到,对l做调整(这会增加相等子图的边),重新找 { if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 1; i <= n; i++) { if(visx[i]) lx[i] -= d; } for(i = 1 ; i <= m; i++) { if(visy[i]) ly[i] += d; else slack[i] -= d; } } }}int main(){ // freopen("g:/1.txt","r",stdin); while(scanf("%d%d",&n,&m)==2) { // memset(w,0,sizeof(w)); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { int c; scanf("%d",&c); w[i][j]=c*100; } int num=0; for(int i=1;i<=n;i++) { int ss; scanf("%d",&ss); num+=w[i][ss]; w[i][ss]++; } KM(); int result = 0 ; for(int i = 1; i <= m; i++) { if(linky[i]!=-1) result += w[linky[i]][i]; } printf("%d %d\n",n-result%100,result/100-num/100); } return 0;}
第八题:hdu 3718
http://acm.hdu.edu.cn/showproblem.php?pid=3718
题目求的是两字符串的最大相似度
思路:因为第一个串的一种字母只能匹配第二个串的一种字母,所以可以转化为求
【字母的最大匹配值/n】 注意输入 scanf--%s 不要用 %c%*c
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m,v;const int maxn = 30;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){ visx[x] = true; for(int y = 1; y <27; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t==0) { visy[y] = true; if(linky[y]==-1 || find(linky[y])) { linky[y] = x; return true; //找到增广轨 } } else if(slack[y] > t) slack[y] = t; } return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}void KM() //返回最优匹配的值{ int i,j; memset(linky,-1,sizeof(linky)); memset(ly,0,sizeof(ly)); for(i = 1; i < 27; i++) { lx[i] = -INF; for(j = 1; j <27; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; } for(int x = 1; x <27; x++) { for(i = 1; i < 27; i++) slack[i] = INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(find(x)) //找到增广轨,退出 break; int d = INF; for(i = 1; i < 27; i++) //没找到,对l做调整(这会增加相等子图的边),重新找 { if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 1; i < 27; i++) { if(visx[i]) lx[i] -= d; } for(i = 1 ; i <27; i++) { if(visy[i]) ly[i] += d; else slack[i] -= d; } } }}int main(){ // freopen("g:/1.txt","r",stdin); char str[10005]; int t;scanf("%d",&t); while(t--) { int v; scanf("%d%d%d%*c",&n,&m,&v); for(int i=0;i < n;i++) { char c[5]; scanf("%s",c); str[i]=c[0]; } for(int i=0;i< v;i++) { memset(w,0,sizeof(w)); char ss[5]; for(int j=0;j < n;j++) { scanf("%s",&ss); w[str[j]-'A'+1][ss[0]-'A'+1]++; } /* for(int kk=1;kk<=27;kk++) { for(int jj=1;jj<=27;jj++) printf("%d",w[kk][jj]); putchar('\n'); }*/ KM(); double result = 0 ; for(int i = 1; i < 27; i++) { if(linky[i]!=-1) result += w[linky[i]][i]; } printf("%.4f\n",result/n); } } return 0;}
推荐题目链接:
http://972169909-qq-com.iteye.com/blog/1184514
- KM算法——带权二分图最佳匹配问题
- 带权二分图最佳匹配KM算法模板
- 带权二分图的最佳匹配(KM算法)
- KM算法--带权二分图最佳匹配
- KM算法——二分图的最佳匹配
- 二分图的最佳完美匹配——KM算法
- 二分图最佳完美匹配——KM算法总结
- 带权二分图匹配KM算法
- 带权二分匹配——KM算法
- KM算法 二分图的最佳匹配
- 二分图的最佳匹配(KM 算法)
- 二分图最佳匹配---KM算法
- 二分图最佳完美匹配-KM算法
- KM算法--带权二分匹配
- 带权的二分图的最优匹配KM算法
- 二分图匹配之最佳匹配 km算法详解
- 二分匹配_最佳匹配KM算法
- 二分图的最佳匹配(KM 算法)
- string[] args 和 string args[]两者的区别
- CSDN开源夏令营总结2014-07-26
- 灵感与文字
- hibernate 一对一 最佳实践
- 数字电路设计之同步时钟采样模块
- KM算法——带权二分图最佳匹配问题
- oracle索引
- oracle参数文件、控制文件、数据文件、日志文件存放位置查看
- JavaScript语言解释器的设计与实现
- 三款开源科学计算软件平台
- 构建基于Javascript的移动CMS——生成博客(一)
- PCI热插拔
- 根据IP获取地址
- 汉诺塔(hanoi)