poj2516 Minimum Cost (最小费用最大流)

分析:

这题我马上想到的是拆点
开始做的时候直接拆了nk+mk个点,这样做是不行的

正解是把k种商品独立,轮流对每个商品建图,
跑k次最小费用最大流,把每次跑出来的费用相加就行了
不需要拆点。

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<algorithm>
#include<map>
typedef long long ll;
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int N=400;
const int M=6e5+5;
struct Node{
    int from,to,nt,cap,flow,cost;
}e[M];
int head[N];
int cnt;
int d[N];
int mark[N];
int pre[N];
int n,m,k;
int s,t;
int mincost,maxflow;//总货物数量和总花费。
int need[N][N];//保存每个人需要的每个商品数量
int have[N][N];//保存每个供应商有的每个商品的数量
int kcost[N][N][N];//保存成本花费
void init(){
    cnt=-1;
    memset(head,-1,sizeof head);
    s=0;
    t=n+m+1;
}
void add(int a,int b,int c,int d){
    cnt++;
    e[cnt].nt=head[a];
    head[a]=cnt;
    e[cnt].from=a;
    e[cnt].to=b;
    e[cnt].cap=c;
    e[cnt].flow=0;
    e[cnt].cost=d;
    cnt++;
    e[cnt].nt=head[b];
    head[b]=cnt;
    e[cnt].from=b;
    e[cnt].to=a;
    e[cnt].cap=0;
    e[cnt].flow=0;
    e[cnt].cost=-d;
}
bool spfa(){
    memset(mark,0,sizeof mark);
    memset(d,inf,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=0;
    mark[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        mark[x]=0;
        for(int i=head[x];i!=-1;i=e[i].nt){
            int v=e[i].to;
            if(e[i].cap>e[i].flow&&d[v]>d[x]+e[i].cost){
                d[v]=d[x]+e[i].cost;
                pre[v]=i;
                if(!mark[v]){
                    q.push(v);
                    mark[v]=1;
                }
            }
        }
    }
    return d[t]!=inf;
}
void ek(){
    mincost=maxflow=0;
    while(spfa()){
        int k=inf;
        for(int i=t;i!=s;i=e[pre[i]].from){
            k=min(k,e[pre[i]].cap-e[pre[i]].flow);
        }
        for(int i=t;i!=s;i=e[pre[i]].from){
            e[pre[i]].flow+=k;
            e[pre[i]^1].flow-=k;
        }
        maxflow+=k;
        mincost+=k*d[t];
    }
}
int main(){
    while(scanf("%d%d%d",&n,&m,&k)!=EOF){
        if(n+m+k==0)break;
        init();
        int sum=0;//需要的货物总数
        for(int i=1;i<=n;i++){//人
            for(int j=1;j<=k;j++){//货物
                scanf("%d",&need[i][j]);//数量
                sum+=need[i][j];
            }
        }
        for(int i=1;i<=m;i++){//供应点
            for(int j=1;j<=k;j++){//货物
                scanf("%d",&have[i][j]);//数量
            }
        }
        for(int c=1;c<=k;c++){//货物
            for(int i=1;i<=n;i++){//店主
                for(int j=1;j<=m;j++){//供应点
                    scanf("%d",&kcost[c][i][j]);//成本
                }
            }
        }
        int allcost=0;
        int allflow=0;
        for(int j=1;j<=k;j++){//货物
            init();
            for(int i=1;i<=n;i++){//人
                add(s,i,need[i][j],0);//源点到人
            }
            for(int i=1;i<=n;i++){//人到供应商
                for(int c=1;c<=m;c++){
                    add(i,c+n,need[i][j],kcost[j][i][c]);
                }
            }
            for(int i=1;i<=m;i++){
                add(i+n,t,have[i][j],0);
            }
            ek();
            allcost+=mincost;
            allflow+=maxflow;
        }
        if(allflow==sum){//判断是否满足所有人
            printf("%d\n",allcost);
        }else{
            printf("-1\n");
        }
    }
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值