蓝桥杯 费用流裸题 最小费用最大流 Java

问题描述

一个有向图,求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;
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值