hdu 3667 Transportation(最小费用最大流+拆边)

本文介绍了一种解决最小费用最大流问题的方法,特别是当费用与流量的平方成正比的情况。通过拆边法将问题转化为标准的最小费用最大流问题,并提供了一个具体的实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

求从城市1运送K单位物品到城市n的最小花费。给定的有向边,每条边都有其容量c,并且,产生的费用是 a * ( f * f ),其中f是这条边上的流量,a是给出的系数。

思路:

这个题目就是刘汝佳训练指南上建模与建图的一种,费用与流量的平方成正比的最小流。容量c均为整数,并且每条弧还有一个费用系数a,表示该弧流量为x时费用为a*x*x,如何求最小费用最大流?

用拆边法,如图

图更正:   最后一个cost=9a,(图有点丑)

一个费用系数为a,容量为5的边被拆成5条容量为1费用各异的弧,因为求的是最小费用流,如果这条弧的流量为1,走的肯定是cost=1的那条弧;如果流量为2,肯定走的是cost=1和cost=3的那两条;如果流量为3,走的肯定是 cost=1,3,5那三条。。。。。。不难验证,不管流量是1~5中间的哪一个,左右两个图都是等价的。这样就转化为普通的最小费用最大流问题。需要注意的是,因为有重边,普通的邻接矩阵无法满足要求。要么采用模板 最小费用最大流(刘汝佳 p363),要么采用把邻接矩阵加一维,表示某两点间的第几条弧。

此题分析:给定一条边(u,v),其计费系数为a,容量为c,那么可以把(u,v)拆成5条边,费用为(1a,3a,5a,7a,9a),容量都为1,。对所有输入的有向边,按照上述方法拆边建图,然后加入超级源点s(0),s连向1,容量为K,费用为0。然后跑一遍最小费用最大流,若流量不等于K,则输出-1,否则输出最小费用。

代码:

#include<map>  
#include<set>  
#include<cmath>  
#include<queue>  
#include<stack>  
#include<ctime>  
#include<cctype>  
#include<string>  
#include<cstdio>  
#include<cstring>  
#include<cstdlib>  
#include<iostream>  
#include<algorithm>  
using namespace std;  
  
#define end() return 0  
  
typedef long long ll;  
typedef unsigned int uint;  
typedef unsigned long long ull;  
  
const int maxn = 100 + 5;  
const int INF = 0x7f7f7f7f;  
  
struct Edge{  
    int from,to,cap,flow,cost;  
    Edge(int u,int v,int c,int f,int w):from(u),to(v),cap(c),flow(f),cost(w){}  
};  
  
struct MCMF{  
    int n,m,flow,cost;  
    vector<Edge>edge; //边数的两倍  
    vector<int>G[maxn]; //邻接表,G[i][j]表示i的第j条边在e数组中的序号  
    int inq[maxn]; //是否在队列  
    int d[maxn]; //Bellman-Ford  
    int p[maxn]; //上一条弧  
    int a[maxn]; //可改进量  
  
    void init(int n){  
        this -> n = n;  
        for(int i=0;i<=n;i++) G[i].clear();  
        edge.clear();  
    }  
  
    void addEdge(int from,int to,int cap,int cost){  
        edge.push_back(Edge(from,to,cap,0,cost));  
        edge.push_back(Edge(to,from,0,0,-cost));  
        m=edge.size();  
        G[from].push_back(m-2);  
        G[to].push_back(m-1);  
    }  
  
    bool BellmanFord(int s,int t,int& flow,int& cost){  
        memset(d,INF,sizeof(d));  
        memset(inq,0,sizeof(inq));  
        d[s]=0; inq[s]=1; p[s]=0; a[s]=INF;  
  
        queue<int>q;  
        q.push(s);  
        while(!q.empty()){  
            int u=q.front();q.pop();  
            inq[u]=0;  
            for(int i=0;i<G[u].size();i++){  
                Edge& e=edge[G[u][i]];  
                if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){  
                    d[e.to]=d[u]+e.cost;  
                    p[e.to]=G[u][i];  
                    a[e.to]=min(a[u],e.cap-e.flow);  
                    if(!inq[e.to]){  
                        q.push(e.to);  
                        inq[e.to]=1;  
                    }  
                }  
            }  
        }  
  
        if(d[t]==INF) return false;  
        flow+=a[t];  
        cost+=d[t]*a[t];  
        for(int u=t;u!=s;u=edge[p[u]].from){  
            edge[p[u]].flow+=a[t];  
            edge[p[u]^1].flow-=a[t];  
        }  
        return true;  
    }  
  
    //需要保证初始网络中没有负权圈  
    void MincostMaxflow(int s,int t){  
        flow=0,cost=0;  
        while(BellmanFord(s,t,flow,cost));  
    }  
};  
  
struct EDGE{  
    int u,v,a,c;  
    EDGE(){}  
    EDGE(int u,int v,int a,int c):u(u),v(v),a(a),c(c){}  
};  
  
int N,M,K;  
int u,v,a,c;  
EDGE edges[5005];  
MCMF mcmf;  
  
void input(){  
    for(int i=1;i<=M;i++){  
        scanf("%d%d%d%d",&u,&v,&a,&c);  
        edges[i]=EDGE(u,v,a,c);  
    }  
}  
  
void createGraph(){  
    mcmf.init(N+1);  
    mcmf.addEdge(0,1,K,0);  
    for(int k=1;k<=M;k++){  
        int x=edges[k].c*edges[k].c;  
        for(int i=1,j=1;j<=x;i+=2,j+=i){  
            mcmf.addEdge(edges[k].u,edges[k].v,1,edges[k].a*i);  
        }  
    }  
}  
  
void solve(){  
    createGraph();  
    mcmf.MincostMaxflow(0,N);  
    if(mcmf.flow==K) printf("%d\n",mcmf.cost);  
    else printf("-1\n");  
}  
  
int main(){  
    while(scanf("%d%d%d",&N,&M,&K)!=EOF){  
        input();  
        solve();  
    }
	return 0;  
}  




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值