一开始想到了就是拆点,题目说每个人对每种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算法,可以参考
http://www.cnblogs.com/one--world--one--dream/archive/2011/08/14/2138385.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。