poj2516(最小费最大流)

这道题用了spa算法,多源点多汇点最大流,最小费用最大流


题意蛮复杂的:

转载请注明出处:優YoU http://blog.csdn.net/lyy289065406/article/details/6742534 

 

大致题意:

       有N个供应商,M个店主,K种物品。每个供应商对每种物品的的供应量已知,每个店主对每种物品的需求量的已知,从不同的供应商运送不同的货物到不同的店主手上需要不同的花费,又已知从供应商Mj送第kind种货物的单位数量到店主Ni手上所需的单位花费。

问:供应是否满足需求?如果满足,最小运费是多少?

 

解题思路:

费用流问题。

 

(1)输入格式

在说解题思路之前,首先说说输入格式,因为本题的输入格式和解题时所构造的图的方向不一致,必须要提及注意。以样例1为例:

 

 

 

(2)题目分析和拆解:

A、首先处理“供应是否满足需求”的问题。

       要总供应满足总需求,就必须有 每种物品的供应总量都分别满足其需求总量,只要有其中一种物品不满足,则说明供不应求,本组数据无解,应该输出-1。但是要注意这里判断无解后,只能做一个标记,但还要继续输入,不然一旦中断输入,后面的几组数据结果就全错了。

       而要知道“每种物品的供应总量都分别满足其需求总量”,对所有供应商第kind种物品的供应量求和ksupp[kind],对所有店主第kind种物品的需求量求和kneed[kind],然后比较ksupp[kind]与kneed[kind]就可以了。

                     而最小费用流的计算是建立在“供等于求”或“供过于求”的基础上的。

 

       B、最小费用问题

       要直接求出“把所有物品从所有供应商运送到所有店主的最小费用MinTotalCost”是不容易的。但是求出“把第kind种物品从所有供应商运送到所有店主的最小费用MinCost[kind]”却简单得多,这就转化为经典的多源多汇的费用流问题,而最后只需要把K种物品的最小费用求和 MinCost[kind],就能得到运送所有物品的最小费用MinTotalCost。

其实题目的输入方式最后要输入K个矩阵已经暗示了我们要拆解处理。

 

       C、构图

                 那么对于第kind种物品如何构图呢?

解决多源多汇网络问题,必须先构造与其等价的单源单汇网络。构造超级源s和超级汇t,定义各点编号如下:

 超级源s编号为0,供应商编号从1到M,店主编号从M+1到M+N,超级汇t编号为M+N+1。

令总结点数Nump=M+N+2,申请每条边的“花费”空间cost[Nump][ Nump]和“容量”空间cap[Nump][ Nump],并初始化为全0。

超级源s向所有供应商M建边,费用为0,容量为供应商j的供应量。

       每个供应商都向每个店主建边,正向弧费用为输入数据的第kind个矩阵(注意方向不同),容量为供应商j的供应量;反向弧费用为正向弧费用的负数,容量为0。

所有店主向超级汇t建边,费用为0,容量为店主i的需求量。

 

注意:1、其他没有提及的边,费用和容量均为0,容量为0表示饱和边或不连通。

   2、计算每种物品的最小费用都要重复上述工作重新构图,不过存储空间cost和cap不必释放,可重新赋值再次利用。

      

       D、求解

       对于第kind种物品的图,都用spfa算法求解最小费用路径(增广链),再利用可分配最大流调MaxFlow整增广链上的容量,正向弧容量减去MaxFlow,反向弧容量减去MaxFlow,费用为单位花费乘以MaxFlow。

       具体的算法流程可参考我POJ2195的解题报告,基本一样。但注意的导致本题无可行解的原因只有“供不应求”,由输入数据知显然各边的容量均>=0,因此并不会出现负权环,spfa仍然用while循环直至无增广链为止足矣。


代码:

(侵删)

建图,对每个仓库是一个结点,每个客户也是一个结点,除此之外再加上s源点和t结束点!
1、s到仓库i的边的权值为仓库i的供给量
2、仓库i到客户j的权值为仓库的供给量
3、客户j到t的权值为客户的需求量
4、还有建立花费图,cost[i][j]代表仓库i到客户j的没一个物品的运费
然后用spfa算法求s到t的最小路径(路径上边的权值不为0),如果到t的最短路径有更新(此条路径话费最少),则记录这条路径,求出这条路径上权值最小的边的权值,然后这条路径上所有的边的权值都减去该边的权值min,此时最小的花费增加了min*在这条边上的花费!如果s到t的值没有更新,则对于k物品的最小花费则已经求出来了!


#include<algorithm>
#include<iostream>
#include<cstdio>
#include<string.h>
#include<cmath>
#include<queue>
using namespace std;
#define min(a,b) (a)<(b)?(a):(b)
#define N 130
#define inf 100000000
#define INT_MAX 10000000
int need[N][N];
int offer[N][N];
int map[N][N],cost[N][N];
int sum_need[N],sum_offer[N];
int n,m,K;
int pre[N];
int s,t;//源点和结束点
int spfa()
{
    int dis[N];
    bool vis[N];
    int i;
    queue<int>q;
    for(i=0; i<=t; i++)
    {
        vis[i]=false;
        dis[i]=inf;
    }
    vis[s]=true;
    dis[s]=0;
    q.push(s);
    while(!q.empty())
    {
        int k=q.front();
        vis[k]=false;
        q.pop();
        for(i=0; i<=t; i++)
        {
            if(map[k][i]&&dis[i]>dis[k]+cost[k][i])
            {
                dis[i]=dis[k]+cost[k][i];
                pre[i]=k;
                if(vis[i]==false)
                {
                    vis[i]=true;
                    q.push(i);
                }
            }
        }
    }
    if(dis[t]!=inf)
        return 1;
    else
        return 0;
}
int fond()
{
    int i,j;
    int Min=INT_MAX;
    j=0;
    while(spfa())
    {
        for(i=t; i!=s; i=pre[i])
            Min=min(Min,map[pre[i]][i]);
        for(i=t; i!=s; i=pre[i])
        {
            map[pre[i]][i]-=Min;
            map[i][pre[i]]+=Min;
            j+=cost[pre[i]][i]*Min;
        }
    }
    return j;
}
int main()
{
    int i,j,k,sum,sign;
    while(scanf("%d%d%d",&n,&m,&K))
    {
        if(n==0&&m==0&&K==0)
            break;
        sum=0;
        memset(sum_need,0,sizeof(sum_need));
        memset(sum_offer,0,sizeof(sum_offer));
        for(i=1; i<=n; i++)
            for(j=1; j<=K; j++)
            {
                scanf("%d",&need[i][j]);
                sum_need[j]+=need[i][j];
            }
        for(i=1; i<=m; i++)
            for(j=1; j<=K; j++)
            {
                scanf("%d",&offer[i][j]);
                sum_offer[j]+=offer[i][j];
            }
        sign=0;
        for(i=1; i<=K; i++)
            if(sum_offer[i]<sum_need[i])
            {
                sign=1;
                break;
            }
        s=0;
        t=m+n+1;
        for(k=1; k<=K; k++)
        {
            for(i=0; i<=t; i++)
                for(j=0; j<=t; j++)
                    map[i][j]=cost[i][j]=0;
            for(i=1+m; i<=n+m; i++)
                for(j=1; j<=m; j++)
                {
                    scanf("%d",&cost[j][i]);
                    cost[i][j]=-cost[j][i];
                }
            if(sign==1)
                continue;
            for(i=1; i<=m; i++)
                map[s][i]=offer[i][k];
            for(i=1; i<=m; i++)
                for(j=1+m; j<=n+m; j++)
                    map[i][j]=offer[i][k];
            for(i=m+1; i<=m+n; i++)
                map[i][t]=need[i-m][k];
            sum+=fond();
        }
        if(sign==1)
            printf("-1\n");
        else
            printf("%d\n",sum);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值