算法设计与分析 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;
}