//类型:最小费用流问题(此题的关键在于建图)
算法:最小费用路算法(见《算法分析与设计》P312页)
构图:由于有K种商品,但是每一种商品的图其实是独立的。所以,我们可以构K次二分图。每次,新增源点src和汇点des,src向提供者连弧,
容量为给定值,费用为0;提供者向购买者连弧,容量应为给定值(如果容量为maxlongint则会超时),费用为给定值;购买者向des连弧,容量
为给定值,费用为0。显然,这个图我们求最小费用最大流即可,当然也可以用KM求最佳匹配。由于要求K次最小费用最大流,所以时间复杂度要乘以个k。
最小费用流问题要特别注意费用要建立反向边。
#include <iostream>
#include <queue>
//#include <conio.h>
using namespace std;
int maxData = 10000000;
bool final[105]; //SPFA算法中标识结点是否在队列中
int supply[51];
int demand[51];
int d[105];
int order[51][51];
int store[51][51];
int cost[51][51][51];
int flow[105][105];
int capacity[105][105];
int kcost[105][105];
int pre[105];
int N,M,K;
//残留网络中从源点到汇点的最小费用路是残留网络中从源点到汇点的以费用为权的最短路
void SPFA(int src)
{
queue<int> myqueue;
int i,j;
memset(final,0,sizeof(final));
memset(pre,-1,sizeof(pre));
myqueue.push(src);
for(i=0;i<=N+M+1;++i) //SPFA算法与Dij不同,SPFA初始化时将除源点以外所有点的最短距离初始化无穷大
{
d[i] = maxData;
}
d[src] = 0; //源点最短距离设置成0
final[src] = true;
while(!myqueue.empty()) //SPFA算法可入队列多次
{
int frontint = myqueue.front();myqueue.pop();
final[frontint] = false;
for(i=0;i<=N+M+1;++i)
{
if(capacity[frontint][i]>flow[frontint][i] && d[i]>d[frontint]+kcost[frontint][i]) //c[u][v]>f[u][v]说明<u,v>之间流量可以进行增加
{
d[i] = d[frontint]+kcost[frontint][i];
pre[i] = frontint; //修改前驱
if(!final[i])
{
final[i] = true;
myqueue.push(i);
}
}
}
}
}
void minCost(int src,int des)
{
int minAdd = maxData;
int p;
while(1)
{
SPFA(src);
if(pre[des]==-1) //表示已无增广路
break;
minAdd = maxData;
p = des;
while(pre[p]!=-1)
{
minAdd = min(minAdd,(capacity[pre[p]][p]-flow[pre[p]][p])); //求的增广路的可增流量
p = pre[p];
}
p = des;
while(pre[p]!=-1) //沿着最小费用路进行增广
{
flow[pre[p]][p] += minAdd;
flow[p][pre[p]] -= minAdd;
p = pre[p];
}
}
}
int main()
{
//freopen("2516.txt","r",stdin);
int i,j,t;
int src,des;
int flowcost;
bool flag = true; //是否存在某一产品的需求大于供应
while(scanf("%d%d%d",&N,&M,&K)!=-1)
{
if(N==0 && M==0 && K==0)
break;
flowcost = 0;
memset(supply,0,sizeof(supply)); //存放产品的供应
memset(demand,0,sizeof(demand)); //存放产品的需求
for(i=1;i<=N;++i)
{
for(j=1;j<=K;++j)
{
scanf("%d",&order[i][j]);
demand[j] += order[i][j];
}
}
for(i=1;i<=M;++i)
{
for(j=1;j<=K;++j)
{
scanf("%d",&store[i][j]);
supply[j] += store[i][j];
}
}
for(t=1;t<=K;++t)
{
for(i=1;i<=N;++i)
{
for(j=1;j<=M;++j)
{
scanf("%d",&cost[t][j][i]);
}
}
}
for(i=1;i<=K;++i) //如果某一产品的需求大于供应
{
if(demand[i]>supply[i])
{
printf("-1\n");
flag = false;
break;
}
}
if(!flag)
{
flag = true;
continue;
}
src = 0; //超级源点
des = N+M+1; //超级汇点
//由于商品是独立的,所以按照商品数构图K次
for(t=1;t<=K;++t)
{
memset(capacity,0,sizeof(capacity));
memset(kcost,0,sizeof(kcost));
memset(flow,0,sizeof(flow));
for(j=1;j<=M;++j) //源点到提供者建弧
{
capacity[src][j] = store[j][t];
}
for(j=1;j<=N;++j) //购买者(购买者下标别忘了加M)到汇点建弧
{
capacity[j+M][des] = order[j][t];
}
for(i=1;i<=M;++i) //提供者到购买者建弧
{
for(j=1;j<=N;++j)
{
capacity[i][j+M] = store[i][t]; //此处建图是切忌不要赋值成无穷大,在搜索路径是会超时
kcost[i][j+M] = cost[t][i][j];
kcost[j+M][i] = -kcost[i][j+M]; //负费用,表示回流会减小费用(千万别忘了)
}
}
minCost(src,des);
for(i=1;i<=M;++i)
{
for(j=1;j<=N;++j)
{
flowcost += kcost[i][j+M]*flow[i][j+M]; //最小费用
}
}
}
printf("%d\n",flowcost);
}
//getch();
return 0;
}