您的位置:首页 > 其它

poj 2516 Minimum Cost KM算法 最小权值匹配

2012-03-16 12:02 411 查看
一开始想到了就是拆点,题目说每个人对每种goods的需求都是只有0-3,我是从这个想到的。。。

接下来就是建立模型拉。然后就是KM算法。。

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

using namespace std;

int shop[51][51];

int store[51][51];

int cost[51][51][51];

int sum1[51];

int sum2[51];

int max(int x,int y){

if(x>y)return x;

return y;

}

int min(int x,int y){

if(x>y)return y;

return x;

}

char maze[200][200];/////根据题目要求建二分图的需要

int weight[200][200];///二分图中各个顶点的标号

int match[200];////匹配结果

int lx[200],ly[200];////////记录顶点的标号

int lack;////////当无法增广路时应该缩小的范围

int visx[200],visy[200];//////记录此次的匹配访问过的结点

int num;/////记录总共有多少个顶点

int num2;

bool dfs(int u){/////找增广路,其实和最大匹配差不多

visx[u] = 1;

for(int v = 1;v<num2;++v){

if(!visy[v]){

int t = lx[u]+ly[v]-weight[u][v];

if(t==0){

visy[v] = true;

if(match[v]==-1 ||dfs(match[v]))

{

match[v] = u;

return true;

}

}

else lack = max(lack,t);///////靠这个记录该缩小的范围

}

}

return false;

}

long long KM()

{

int i,j;

for(i =1;i<num;i++){///////初始化

lx[i] = 100000000;

for(j = 1;j<num2;j++)lx[i] = min(lx[i],weight[i][j]);

}

for(int i = 1;i<num2;i++)ly[i] = 0;

memset(match,-1,sizeof(match));

for(int u = 1;u<num;u++)

while(1){

memset(visx,false,sizeof(visx));

memset(visy,false,sizeof(visy));

lack = -10000000;

if(dfs(u))break;

for(i = 1;i<num;i++){

if(visx[i])lx[i]-=lack;

}

for(int i = 1;i<num2;i++)if(visy[i])ly[i]+=lack;

}

long long ans = 0;

for(i = 1;i<num2;i++)if(match[i]!=-1)ans+=weight[match[i]][i];

return ans;

}

int main(){

int n,m,k;

while(scanf("%d%d%d",&n,&m,&k),n!=0 && m!=0 && k!=0){

memset(sum1,0,sizeof(sum1));

memset(sum2,0,sizeof(sum2));

for(int i = 1;i<=n;i++){

for(int j = 1;j<=k;j++){

scanf("%d",&shop[i][j]);

sum1[j]+=shop[i][j];

}

}

for(int i = 1;i<=m;i++)

for(int j = 1;j<=k;j++){

scanf("%d",&store[i][j]);

sum2[j]+=store[i][j];

}

for(int i = 1;i<=k;i++)

for(int j = 1;j<=n;j++)

for(int u = 1;u<=m;u++){

scanf("%d",&cost[i][j][u]);

}

bool ans = true;

for(int i = 1;i<=k;i++)if(sum1[i]>sum2[i]){

ans = false;

break;

}

if(!ans)printf("-1\n");

else {

int re = 0;

for(int i = 1;i<=k;i++){

if(sum1[i]==0)continue;

num = 1;

sum2[0] = 0;

for(int j = 1;j<=m;j++){

sum2[j] =sum2[j-1]+store[j][i];

}

num2 = sum2[m]+1;

num = 1;

for(int j = 1;j<=n;j++){

for(int u = 1;u<=shop[j][i];u++){

for(int ii = 1;ii<=m;ii++){

for(int jj = sum2[ii-1]+1;jj<=sum2[ii];jj++){

weight[num][jj] = cost[i][j][ii];

}

}

num++;

}

}

/*printf("\n\n");

for(int ii = 1;ii<num;ii++){

for(int j = 1;j<num2;j++)printf("%d ",weight[ii][j]);

printf("\n");

}*/

int s = KM();

printf("%d\n",s);

re+=s;

}

printf("%d\n",re);

}

}

}

关于KM算法,可以参考

/article/7119573.html

下面的就是来自这个网址的,KM算法的理解个人觉得是最优子图还有那个定理,只要理解了,后面的就都简单了。。

带权二分图的最优匹配 Kuhn-Munkres算法

问题背景:
分工问题如下:某公司有工作人员x1,x2,...,xn,他们去做工作y1,y2,...,yn,每人适合做其中的一项或几项工作,每个人做不同的工作的效益不一样,我们需要制定一个分工方案,使公司的总效益最大,这就是所谓最佳分配问题, 它们数学模型如下:
数学模型:

G是加权完全二分图,V(G)的二分图划分为X,Y;X={x1,...,xn},Y={y1,y2,...yn},w(xiyi)>=0是工作人员xi做yi工作时的效益,求权最大的完备匹配,这种完备匹配称为最佳匹配。

这个问题好象比较的棘手,用穷举法的话举也举死了,效率很低。本节给出一种有效算法,为此,先引入一个定义和一个定理。

定义1 映射l:V(G)->R,满足:任意x∈X,任意y∈Y,成立

l(x)+l(y)>=w(xy),

则称l(v)是二分图G的可行顶标;令

El={xy|xy∈E(G),l(x)+l(y)=w(xy)},

称以El为边集的G之生成子图为相等子图,记为Gl

可行顶标是存在的,例如

l(x)=max w(xy),x∈X;

l(y)=0, y∈Y.

定理1 Gl的完备匹配即为G的最佳匹配。

证:设M*是Gl的一个完备匹配,因Gl是G的生成子图,故M*也是G的完备匹配。M*中的边之端点集合含G的每个顶点恰一次,所以

W(M*)=Σw(e)=Σl(v) (e∈M*,v∈V(G)).

另一方面,若M是G中任意一个完备匹配,则

W(M)=Σw(e)<=Σl(v) (e∈M,v∈V(G)),

所以

W(M*)>=W(M),

即M*是最佳匹配,证毕。

定理1告知,欲求二分图的最佳匹配,只需用匈牙利算法求取其相等子图的完备匹配;问题是,当Gl中无完备匹配时怎么办?Kuhn和Munkras给出修改顶标的一个算法,使新的相等子图的最大匹配逐渐扩大,最后出现相等子图的完备匹配。

Kuhn-Munkras算法:

(0) 选定初始的可行顶标l,确定Gl,在Gl中选取一个匹配M。

(1) X中顶皆被M许配,止,M即为最佳匹配;否则,取Gl中未被M许配的顶u,令S={u},T为空。

(2) 若N(S)真包含T,转(3);若N(S)=T,取

al=min(l(x)+l(y)-w(xy)}(x∈S,y∈T),

l(v)-al,v∈S;

l(v)= l(v)+al,v∈T;

l(v),其它。

l=l,Gl=Gl。

(3) 选N(S)-T中一顶y,若y已被M许配,且yz∈M,则S=S∪{z},T=T∪{y},转(2);否则,取Gl中一个M的可增广轨P(u,y),令M=M⊙E(P),转(1)。

上面的算法看得有点莫名,改那个可行顶标怎么改改就好了?还是得看盾例子

例1 已知K5,5的权矩阵为

y1 y2 y3 y4 y5

x1 3 5 5 4 1

x2 2 2 0 2 2

x3 2 4 4 1 0

x4 0 1 1 0 0

x5 1 2 1 3 3

求最佳匹配,其中K5,5的顶划分为X={xi},Y={yi},i=1,2,3,4,5.

解:
(1)取可行顶标l(v)为 l(yi)=0,i=1,2,3,4,5;l(x1)=max(3,5,5,4,1}=5,l(x2)=max{2,2,0,2,2}=2,l(x3)=max(2,4,4,1,0}=4,l(x4)=max{0,1,1,0,0}=1,l(x5)=max{1,2,1,3,3}=3.

(2) Gl及其上之匹配见图7.12。

这个图中ο(G-x2)=3,由Tutte定理知无完备匹配。需要修改顶标。
(3) u=x4,得S={x4,x3,x1},T={y3,y2},N(S)=T,于是

al=min(l(x)+l(y)-w(xy)}=1. (x∈S,y∈T)

x1,x2,x3,x4,x5的顶标分别修改成4,2,3,0,3;y1,y2,y3,y4,y5的顶标分别修改成0,1,1,0,0。

(4) 用修改后的顶标l得Gl及其上面的一个完备匹配如图7.13。图中粗实线给出了一个最佳匹配,其最大权是2+4+1+4+3=14。

我们看出:al>0;修改后的顶标仍是可行顶标;Gl中仍含Gl中的匹配M;Gl中至少会出现不属于M的一条边,所以会造成M的逐渐增广。
得到可行顶标后求最大匹配:
书上这部分没讲,实际上是这样的,对于上面这个例子来说,通过Kuhn-Munkres得到了顶标l(x)={4,2,3,0,3},l(y)={0,1,1,0,0},那么,对于所有的l(xi)+l(yj) = w(i,j),在二分图G设置存在边w(i,j)。再用匈牙利算法求出最大匹配,再把匹配中的每一边的权值加起来就是最后的结果了。





KM算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。设顶点Xi的顶标 为A[i],顶点Yi的顶标为B[i],顶点Xi与Yj之间的边权为w[i,j]。在算法执行过程中的任一时刻,对于任一条边 (i,j),A[i]+B[j]>=w[i,j]始终成立。KM算法的正确性基于以下定理:

若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。

这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。

初始时为了使A[i]+B[j]>=w[i,j]恒成立,令A[i]为所有与顶点Xi关联的边的最大权,B[j]=0。如果当前的相等子图没有完备匹配,就按下面的方法修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止。

我们求当前相等子图的完备匹配失败了,是因为对于某个X顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,它的叶子结点全部是X顶点。现在我们把交错树中X顶点的顶标全都减小某个值d,Y顶点的顶标全都增加同一个值d,那么我们会发现:

两端都在交错树中的边(i,j),A[i]+B[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。

两端都不在交错树中的边(i,j),A[i]和B[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。

X端不在交错树中,Y端在交错树中的边(i,j),它的A[i]+B[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。

X端在交错树中,Y端不在交错树中的边(i,j),它的A[i]+B[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。

  现在的问题就是求d值了。为了使A[i]+B[j]>=w[i,j]始终成立,且至少有一条边进入相等子图,d应该等于min{A[i]+B[j]-w[i,j]|Xi在交错树中,Yi不在交错树中}。

  以上就是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值即可。但还要注意一点:修改顶标后,要把所有的slack值都减去d。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: