算法设计与分析 6.4 费用网络

★题目描述

有一个N个点M条边的有向无环图,每条边有容量和其单位容量的花费

请求出从起点1到终点N的最大流量及其最小花费

★输入格式

输入的第一行两个数字N,M(1<=N,M<=100,1<=K<=100)$,表示点数、边数。

接下来M行每行三个数字a,b,c,d(1<=a,b<=N,1<=c<=100,1<=d<=100)代表节点a到b有一条容量为c的边且该边每单位流量需要花费d费用。

★输出格式

输出最大流量及其最小费用。

★样例输入

2 1
1 2 3 4

★样例输出

3 12

★提示

★参考代码

/*
网络流问题

一、概念部分
	每条边的流量:flow(u,v)
	每条边的容量:cap(u,v)
	
	
	可行流:在容量网络G中满足以下条件的网络流f,称为可行流.
	a.弧流量限制条件:0<=f(u,v)<=c(u,v);
	b:平衡条件:即流入一个点的流量要等于流出这个点的流量,(源点和汇点除外).

	增广路:如果一个可行流不是最大流,那么当前网络中一定存在一条增广路
	什么是增广路?设f是一个容量网络G中的一个可行流,P是从Vs到Vt 的一条链,若P满足以下条件:
	a.P中所有前向弧(方向与链的正方向一致的弧)都是非饱和弧, f<c 
	b.P中所有后向弧(方向与链的正方向相反的弧)都是非零弧. f>0
	则称P为关于可行流f 的一条增广路.

	残余网络: 
	在一个网络流图上,找到一条源到汇的路径后,对该条路径上所有的边,其容量都减去此次找到的量,
	对路径上所有的边,都添加一条反向边,其容量等于此次找到的最小流量,这样得到的新图,就称为原图的“残余网络”

	费用流:cost*flow
	
	最大流等于小于最小容量 
	

二、算法的精华部分,利用反向边,使程序有了一个后悔和改正的机会

	那么我们刚刚的算法问题在哪里呢?
	问题就在于我们没有给程序一个”后悔”的机会,应该有一个不走(2-3-4)而改走(2-4)的机制。
	那么如何解决这个问题呢?回溯搜索吗?那么我们的效率就上升到指数级了。
	
	这时再找增广路的时候,就会找到1-3-2-4这条可增广量,即delta值为1的可增广路。将这条路增广之后,得到了最大流2。


三、找最大流的步骤
	step1 找到一条源到汇的增广路径。0<f<c 
	step2 使用该增广路径,构建残余网络。这条增广路径上容量改为剩余容量,而图中所有边都添加一条反向边,其容量等于此次找到的最小流量
	step3 在残余网络上寻找新的增广路径,使总流量增加。回到step2
	step4 直到残余网络上找不到从源到汇的增广路径为止,最大流就算出来了。
 

四、常用的查找最大流的算法
	在每次增广的时候,选择从源到汇的具有最少边数的增广路径。
	也就是说!不是通过dfs寻找增广路径,而是通过bfs寻找增广路径。 
	这就是Edmonds-Karp 最短增广路算法 已经证明这种算法的复杂度上限为nm2 (n是点数,m是边数)
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=1210;

bool vis[maxn];
int n,m;
int dis[maxn],pre[maxn],last[maxn],flow[maxn];//dis最小花费;pre每个点的前驱;last每个点的所连的前一条边;flow源点到此处的流量 
int maxflow,mincost;

struct Edge{
    int to,next,flow,dis;//flow流量 dis花费 
}edge[maxn]; //保存的数据是边 
queue <int> q; //保存的数据是点(从起点到终点) 

int head[maxn],num_edge;
void add_edge(int from, int to, int flow, int dis){
    ++num_edge;
    edge[num_edge].to=to; //边的端点 
	edge[num_edge].next=head[from]; 
    head[from]=num_edge; //邻接表 
    edge[num_edge].flow=flow;
    edge[num_edge].dis=dis;
}

//使用深度优先DFS方法查找所有的增广路径 
int spfa(int s,int t){
    memset(dis,0x7f,sizeof(dis));
    memset(flow,0x7f,sizeof(flow));
    memset(vis,0,sizeof(vis));
    q.push(s); vis[s]=1; dis[s]=0; pre[t]=-1;

    while (!q.empty()) {
        int now=q.front(); q.pop();
        vis[now]=0; 
        for(int i=head[now]; i!=-1; i=edge[i].next) {//从前端点now出发连着很多的后端点i 
            if(edge[i].flow>0 && dis[edge[i].to]>dis[now]+edge[i].dis) { //第i条边还有余量,且会使费用更小 
                dis[edge[i].to]=dis[now]+edge[i].dis;
                pre[edge[i].to]=now; //每个点所连的前端点是now 
                last[edge[i].to]=i; //每个点的所连的前一条边 
                flow[edge[i].to]=min(flow[now],edge[i].flow);//最大流量取决于最小容量 
                if(!vis[edge[i].to]) {
                    vis[edge[i].to]=1;
                    q.push(edge[i].to);
                }
            }
        }
    }
    return pre[n];
}

 
int main(){
    scanf("%d%d",&n,&m);
    
    int a, b, c, d; 
    memset(head,-1,sizeof(head)), num_edge=-1; 
    for (int i=1; i<=m; i++) {
        scanf("%d%d%d%d", &a, &b, &c, &d);
        add_edge(a,b,c,d); 
		add_edge(b,a,0,-d); //反边的流量为0,花费是相反数 
    }
    
    while(spfa(1,n)!=-1) {  //迭代,不断的更新网络 
        maxflow+=flow[n];
        mincost+=flow[n]*dis[n];
        int now=n;
        while(now!=1) {//从源点一直回溯到汇点 
            edge[last[now]].flow-=flow[n];//flow和dis容易搞混 
            edge[last[now]^1].flow+=flow[n];
            now=pre[now];
        }
    }
    
    printf("%d %d",maxflow, mincost);
    return 0;
} 

posted on 2019-12-25 16:55  yejifeng  阅读(438)  评论(0编辑  收藏  举报

导航