问题描述
一个有向图,求1到N的最小费用最大流
输入格式
第一行N M,表示点数与边数
接下来M行每行s t c d表示一条从s到t的容量为c费用为d的边
输出格式
最大流与最小费用
解题思路
SPFA
在能流通的情况下不断找最短增广路。
部分参考了这位作者:Java实现最小费用最大流问题
但是在他的算法中没有考虑两点间有多条边的情况,在找到一条增广路后不能准确找到在增广路里的那条边,如果找错了可能会把这条边的容量减到负数,从而自己生成了负环,提前终止。
在我的改进中增加了cost[],把进入增广路的边的花费cost存到cost[Edge.to],通过该数组找到该边。
package ADV;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
/**
* 费用流裸题
* http://lx.lanqiao.cn/problem.page?gpid=T614
* AC
*
*/
public class ADV318 {
static String[] s;
static int n,m;//n:点数 m:边数
static int MAX = 1000+5;
public static boolean[] used = new boolean[MAX]; //判断顶点是否在队列中
public static int[] cost = new int[MAX]; //为避免两点有多条边时找错边,记录纳入最短增广路径的边的话费,序号为to节点,不能是from
public static int[] pre = new int[MAX]; //记录最短增广路径中相应节点的前节点
public static int[] distance = new int[MAX]; //记录源点到图中其他所有顶点的最短距离
public static int[] capacity = new int[MAX]; //用于记录遍历图每一次得到增广路径的流量,结果是capacity[end]
public static ArrayList<Edge>[] map; //图的邻接表
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
s = in.readLine().split(" ");
n = Integer.valueOf(s[0]);m = Integer.valueOf(s[1]);
int start = 1;
int end = n;
map = new ArrayList[n+1];
for(int i = 1;i <= n;i++)
map[i] = new ArrayList<Edge>();
for (int i = 0; i < m; i++) {
s = in.readLine().split(" ");
int from = Integer.valueOf(s[0]);
int to = Integer.valueOf(s[1]);
int cap = Integer.valueOf(s[2]);
int cost = Integer.valueOf(s[3]);
boolean isMultyEdges = false;
for (int j = 0; j < map[from].size(); j++) {
Edge e = map[from].get(j);
if(e.to == to && e.cost == cost) {//合并两点间花费相同的边
e.cap += cap;
isMultyEdges = true;
break;
}
}
if(isMultyEdges)
continue;
map[from].add(new Edge(from, to, cap, cost)); //正向边
map[to].add(new Edge(to, from, 0, -cost)); //反向边
}
int minCost = 0;//最小费用
int maxFlow = 0;//最大流
while(spfa(start, end)) {
minCost += distance[end] * capacity[end];//最小费用(暂时)
maxFlow += capacity[end];
int last = end;
int begin = end;
// System.out.print("路径为:"+last);
while(begin != start) {
last = begin;
begin = pre[last];//last的前一点,增广路的一部分为begin->last
int i = 0, j = 0;
// System.out.print(" "+last+"<- "+begin);
for(;i < map[begin].size();i++) {//找到begin->last这条边
if(cost[last] == map[begin].get(i).cost&&map[begin].get(i).to == last)
break;
}
// System.out.print(" 原容量:"+map[begin].get(i).cap);
map[begin].get(i).cap -= capacity[end]; //正向边剩余流量减少
// System.out.print(" 减去容量:"+capacity[end]+" 剩余流量:"+map[begin].get(i).cap);
for(;j < map[last].size();j++) {//找到last->begin这条反向边,反向边的花费是负的
if((-cost[last]) == map[last].get(j).cost&&map[last].get(j).to == begin)
break;
}
map[last].get(j).cap += capacity[end]; //反向边剩余流量增加
// System.out.print("剩余流量:"+map[last].get(j).cap);
}
// System.out.println(" 流量:"+capacity[end]);
}
System.out.println(maxFlow+" "+minCost);
}
//寻找一次顶点start到顶点end的最短路径(PS:即费用最少的一条增广路径)
public static boolean spfa(int start, int end) {
Arrays.fill(distance, 0x3f3f3f3f);
Arrays.fill(capacity, 0x3f3f3f3f);
Arrays.fill(cost, 0x3f3f3f3f);
Arrays.fill(pre, -1);
Arrays.fill(used, false);
LinkedList<Integer> queue = new LinkedList<Integer>();
int[] count = new int[n+1];//计算该点进入队列的次数,判断负环
queue.addLast(start);
count[start]++;
used[start] = true;
distance[start] = 0;
while(!queue.isEmpty()) {
int index = queue.removeFirst();//出队,对所有与该点相邻的点进行松弛
used[index] = false;
for (int i = 0; i < map[index].size(); i++) {
Edge edge = map[index].get(i);
if(edge.cap>0 && distance[edge.to]>distance[index]+edge.cost) {//该边有容量且能够用index->to这条边松弛start到to的距离
distance[edge.to] = distance[index]+edge.cost;
cost[edge.to] = edge.cost;//如果是edge.from会在from到其它临界点松弛时覆盖掉,而从edge.to到edge.from不一定是到终点的增广路径
pre[edge.to] = index;
//意义:从起点到edge.from,走edge.to是最近的点。所以倒推能保存最短路,记住start是从哪个点到to最短
capacity[edge.to] = Math.min(capacity[index], edge.cap); //记录增广路径能够流通的最大流量(假设to为汇点)
if(!used[edge.to]) {//to不在队列中
queue.addLast(edge.to);
used[edge.to] = true;
count[edge.to]++;
if(count[edge.to] > n) //用于判断图中是否有负环
return false;
}
}
}
}
if(pre[end]!=-1)
return true;
return false;
}
static class Edge {
public int from; //边的起点
public int to; //边的终点
public int cap; //边的容量
public int cost; //边的费用
public Edge(int from, int to, int cap, int cost) {
this.from = from;
this.to = to;
this.cap = cap;
this.cost = cost;
}
}
}