POJ 2516 Minimum Cost

典型的利用最小费用最大流求解的问题。

题目的要求是根据供应和需求,若可以满足需求,则输出最小费用,否则则输出-1.

首先应该明确这道题应分解为对每个货物种类进行分析,最后汇总结果。对所有种类的货物都进行一遍maxflow后将得到的结果汇总即可。

下面是对任意一种货物进行maxflow的思路。

在初次读题时,感觉在输入cost时题目所要求的input顺序很别扭,明明是从storage到shop的cost,可以标准输入却要以shop为行,正好颠倒了。其实这正是为了更加简化的套用最小费用最大流的方法,即采用逆向思维,假设货物是从shop运到storage的,这时需要添加超级源点和超级汇点。除了这两个超级点外,每个商店和每个storage都是图中的点。若u,v为两点,则(u,v).remain表示两点中流的限制。注意商店之间不可能有流,所以商店间的边的capacity为0,而商店和storage间的边的capacity为inf(代表没有流量上限)。超级源点s到每个shop的边的capacity为该商店对当前讨论的货物的需求,而各个storage和超级汇点的边的remain为对应的storage中讨论货物的库存。这样建模后,如果最后算出的最大流中从超级源点出发的总流量小于对该货物的需求,则表示供不应求,即最终应输出-1.否则则应输出其最小费用。

至于maxflow的具体算法,我的思路是使用spfa找到增广路径。spfa算法是shortest path faster algorithm的缩写,其具体的实现非常简单,需要注意的是要因为增广路径要在残存网络中找,所以可能存在负cost的情况,spfa算法正好可以有能力处理负向边,我们只需根据找到路径中边的顶点来判断正向还是负向即可。

代码很挫,如下:

#include <iostream>
#include <memory.h>
#include <queue>
using namespace std;
int shopnum;
int goodsnum;
int storagenum;
bool visited[102];
int dist[102];
short int needs[51][51];//needs[i][j]表示第i个shop需要第j种货物的数量
int costs[51][51][51];//costs[i][j][k]表示第i个货物从第k个storage运到第j个shop的单位运费
int remain[102][102];
short int storage[51][51];//storage[i][j]表示第i个货站存第k中货物的数量
int fa[102];
int totalcost;
bool spfa(int k)
{
	for(int i=0;i<=shopnum+storagenum+1;i++)  
    {  
        dist[i]=999;  
        visited[i]=false;  
    }  
    dist[0]=0;  
	
	queue<int> mq;
	mq.push(0);
	visited[0]=true;
	while(mq.size()!=0)
	{
		int cur=mq.front();
		mq.pop();
		for(int i=1;i<=shopnum+storagenum+1;i++)
		{
			int curcost;
			if(i>cur)
				curcost=costs[k][cur][i-shopnum];
			else
				curcost=-costs[k][i][cur-shopnum];
			if(remain[cur][i]!=0&&dist[i]>dist[cur]+curcost)
			{
				fa[i]=cur;
				dist[i]=dist[cur]+curcost;
				if(visited[i]==false)
				{	
					mq.push(i);
					visited[i]=true;
				}
			
			}
		}
		visited[cur]=false;

	}
	if(dist[shopnum+storagenum+1]!=999)
		return true;
	else
		return false;

}

int FordFulkerson(int k)
{
	//remain保存着残存网络的可改变容量,初始时和capacity相同,编号为0的为源点,vertex-1的为汇点
	//增加一个虚拟的源节点和汇聚节点,令源节点到shop的边的最大容量为该shop对第k个物品的需求
	//令storage到汇聚节点的边的最大容量为无限
	//若最后达到汇聚节点的流的值与所有的需求相同,则表示货物充足可送到,否则返回-1
	totalcost=0;
	memset(remain,0,sizeof(remain));//0表示不通,999表示无限
	int vertex=2+shopnum+storagenum;//一共的节点数
	
	for(int i=1;i<=shopnum;i++)
		remain[0][i]=needs[i][k];
	for(int i=shopnum+1;i<=shopnum+storagenum;i++)
		remain[i][vertex-1]=storage[i-shopnum][k];
	for(int i=1;i<=shopnum;i++)
	{
		for(int j=1;j<=storagenum;j++)
		{
			remain[i][j+shopnum]=999;//999表示边无限制容量
		}
	}
	fa[0]=-1;
	while(spfa(k)!=false)
	{
		//如果可以找到增广路径,则根据该增广路径的最小流对原图进行更改
		//根据fa数组得到该增广路径的每个节点,找到最小流mincost
		int i=vertex-1;
		int j=0;
		int mincost=999;
		while(fa[i]!=-1)
		{
			j=fa[i];
			if(remain[j][i]<mincost)
				mincost=remain[j][i];
			i=j;
		}
		i=vertex-1;
		while(fa[i]!=-1)
		{
			j=fa[i];
			remain[j][i]-=mincost;
			remain[i][j]+=mincost;
			//边为(j,i)
			if(i>j)
				totalcost+=costs[k][j][i-shopnum]*mincost;
			else
				totalcost-=costs[k][i][j-shopnum]*mincost;
			i=j;
		}
	}
	//找到最大流后判断货物是否充足
	
	int sumneed=0;
	for(int i=1;i<=shopnum;i++)
	{
		//sumget+=remain[goodsnum+i][vertex-1];
		sumneed+=remain[0][i];
	}
	
	if(0!=sumneed)
		return -1;
	else
	{
		return totalcost;
	}
}
int main()
{
	while(cin>>shopnum>>storagenum>>goodsnum)
	{
		if(shopnum==0&&storagenum==0&&goodsnum==0)
			break;
		else
		{
			memset(needs,0,sizeof(needs));
			memset(dist,0,sizeof(dist));
			memset(costs,0,sizeof(costs));
			memset(remain,0,sizeof(remain));
			memset(storage,0,sizeof(storage));
			memset(fa,0,sizeof(fa));
			int mcost=0;
			int msum=0;
			for(int i=1;i<=shopnum;i++)
				for(int j=1;j<=goodsnum;j++)
					cin>>needs[i][j];
			for(int i=1;i<=storagenum;i++)
				for(int j=1;j<=goodsnum;j++)
					cin>>storage[i][j];
			for(int k=1;k<=goodsnum;k++)
			{
				for(int i=1;i<=shopnum;i++)
				{
					for(int j=1;j<=storagenum;j++)
					{	

						cin>>costs[k][i][j];
					}
				}
			}
			bool flag=true;
			for(int k=1;k<=goodsnum;k++)
			{
				mcost=FordFulkerson(k);
				if(mcost==-1)
				{
					flag=false;
					break;
				}
				else
					msum+=mcost;
			}
			if(flag==true)
				cout<<msum<<endl;
			else
				cout<<"-1"<<endl;
		}
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值