天天育儿网,内容丰富有趣,生活中的好帮手!
天天育儿网 > 最大流问题模板-java-蓝桥杯-算法训练-网络流裸题

最大流问题模板-java-蓝桥杯-算法训练-网络流裸题

时间:2020-02-23 21:11:15

相关推荐

最大流问题模板-java-蓝桥杯-算法训练-网络流裸题

1. 本题相关资料

题目链接

这是一道求最大流的模板题。

最大流预备知识:dfsbfs图论基础(图的存储)。

网上已经有很多最大流的解析,我就不再唠叨了,还不了解的同学可参考这个博客:什么是最大流及常用求解算法。

2. 本题坑点

一条边可能会出现多次,所以这里用加等于map[sc.nextInt()][sc.nextInt()] += sc.nextInt();测试点4数据错误,m的值为10000,实际上只有1000条边,可以用StreamTokenizer输入数据,可过100%测试点。(我代码中没有用这个,因为在自己测试的时候不方便)这里吐槽一下蓝桥杯练习系统

构建图时:while (in.nextToken()!=StreamTokenizer.TT_EOF)...

3. 代码实现

1. FF(AC-750ms):

就是用dfs查找增广路,更新反向边,反复。

这里我用的是非递归版本,一开始用递归超时,也可能是我代码有问题吧,有兴趣可自行尝试。当然竞赛的话还是上Dinic吧,快太多了。

import java.io.BufferedInputStream;import java.util.Arrays;import java.util.Scanner;import java.util.Stack;public class 网络流裸题_FF_非递归 {static int[][] map;//图static int[] pre;//记录dfs路径,存储dfs搜索树,从汇点可获取路径。static int n, m;public static void main(String[] args) {Scanner sc = new Scanner(new BufferedInputStream(System.in));n = sc.nextInt();m = sc.nextInt();map = new int[n + 1][n + 1];pre=new int[n+1];for (int i = 0; i < m; i++) {map[sc.nextInt()][sc.nextInt()] += sc.nextInt();}sc.close();int res = 0;int r=0;while ((r=dfs(1))>0) {res += r;int a=n,b=pre[n];while(a!=1) {map[a][b]+=r;map[b][a]-=r;a=b;b=pre[b];}}System.out.println(res);}// 非递归版static int dfs(int x) {Stack<Integer> s = new Stack<>();boolean vis[]=new boolean[n+1];s.push(x);int[] flow=new int[n+1];//记录到每个节点的最大流flow[1]=0x3f3f3f3f;while (!s.isEmpty()) {x=s.pop();vis[x]=true;for (int i = 0; i <= n; i++) {if (!vis[i] && map[x][i] > 0) {pre[i] = x;flow[i]=Math.min(flow[x], map[x][i]);if (i == n)return flow[n];s.push(i);}}}return -1;};}

2. EK(AC-670ms):

用bfs代替dfs,因为bfs每次找到的总是最短路径,所以比dfs效率要高些。

这个代码是粘的本站某位博主的,由于后来找不到了,就没贴链接,我自己也写了EK的,但改来改去总是接近超时,直接干Dinic去了。

package 网络流;import java.util.ArrayDeque; //类import java.util.Queue; //接口import java.util.Scanner;public class Main {public static int n;public static int m;public static void main(String[] args) {Scanner scn = new Scanner(System.in);// 输入结点个数和边的个数n = scn.nextInt();m = scn.nextInt();// 因为第一个结点是1不是0,所以我们多申请一些空间,索引为0的位置就不使用了。从索引为1的位置开始放int[][] edge = new int[n + 1][n + 1];for (int i = 0; i < m; i++) {int vertex1 = scn.nextInt();int vertex2 = scn.nextInt();int weight = scn.nextInt();edge[vertex1][vertex2] += weight;// 一条边可能会出现多次}// path[i]是第i个结点的父节点。我们通过在BFS(广度优先遍历)中不断更新path[i]里的数,从而保存一条路径。int[] path = new int[n + 1];int flow = 0;while (BFSfindPath(edge, 1, n, path)==1) {// 如果能够找到增广路径,那么我们在该路径上能够通过的容量就是这个路径上能通过的最小容量。int min = edge[path[n]][n];for (int i = path[n]; i != 1; i = path[i]) {if (edge[path[i]][i] < min && edge[path[i]][i] > 0) {min = edge[path[i]][i];}}flow += min;for (int i = n; i != 1; i = path[i]) {int j = path[i];edge[j][i] -= min;edge[i][j] += min;}}System.out.println(a);System.out.println(flow);}static long a=0;private static int BFSfindPath(int[][] edge, int s, int t, int[] path) {// path[]是通过记录每个结点的父节点,从而记录下一条完整的路径。long b=System.currentTimeMillis();for (int i = 0; i < path.length; i++) {path[i] = 0;}int vertex_num = edge.length;// 用于标记是否已经访问过该结点int[] visited = new int[vertex_num];Queue<Integer> q = new ArrayDeque<Integer>();q.add(s); //将指定元素插入此双端队列的末尾visited[s] = 1;while (!q.isEmpty() ) {int w = q.poll(); //获取并移除此双端队列所表示的队列的头for (int i = 1; i < vertex_num; i++) {if (edge[w][i] > 0 && visited[i] == 0) {q.add(i);visited[i] = 1;path[i] = w;}}}a=a+(System.currentTimeMillis()-b);System.out.println(System.currentTimeMillis()-b);return visited[t];}}

3. Dinic(AC-100ms):

参考大佬的Dinic解析,不过是C语言

Dinic的主要思想:

分层

用bfs遍历整个图,使用deep数组存储每个节点的深度,然后用dfs查找最大流,dfs时下一个节点必须是当前节点层数+1,大大加快dfs效率。dfs优化

而这里的dfs也和FF中的不同,dfs中参数maxL表示到当前节点的最大流,到达汇点后回溯到这个节点,若到汇点的最大流减去到当前节点的最大流大于0,则说明还可以从当前节点继续搜索,知道当前节点流量全部被瓜分或到达不了汇点为止;这样,一次dfs可发挥最大价值。当前弧优化

用一个cur数组存储一次dfs中每个点遍历到了那一条边,下次碰到这个点时就从这条边开始,因为遍历过的边已经发挥了他最大的价值,已经没有在遍历的必要了。

import java.io.IOException;import java.io.StreamTokenizer;import java.util.ArrayDeque;import java.util.Arrays;import java.util.Queue;public class Dinic {static int n, m;//节点数和边数static int edge_num = -1;static Edge[] edge;//所有边static int[] deep;//节点深度static int[] head, cur; //head存储节点的最后一条边在edge中的索引,cur用于备份head,当前弧优化中需改变curstatic Queue<Integer> q = new ArrayDeque<>();public static void main(String[] args) throws IOException {StreamTokenizer in = new StreamTokenizer(System.in);in.nextToken();n = (int) in.nval;in.nextToken();m = (int) in.nval;edge = new Edge[m << 1];head = new int[n + 1];deep = new int[n + 1];Arrays.fill(head, -1);for (int i = 0; i < m; i++) {in.nextToken();int a = (int) in.nval;in.nextToken();int b = (int) in.nval;in.nextToken();add(a, b, (int) in.nval);add(b, a, 0);//这里同时增加正边和反边,后面反向边增加的时候方便,异或即可。}System.out.println(dinic());}static int dinic() {int ans = 0;while (bfs())ans += dfs(1, Integer.MAX_VALUE);return ans;}//寻找增广路static int dfs(int x, int maxL) {int res = 0;int r = 0;if (maxL == 0 || x == n) {return maxL;}for (int i = cur[x]; i != -1; i = edge[i].next) {cur[x] = i;if (deep[edge[i].to] == deep[x] + 1 && (r = dfs(edge[i].to, Math.min(maxL, edge[i].val))) > 0) {res += r;maxL -= r;edge[i].val -= r;edge[i ^ 1].val += r;if (maxL == 0)break;}}return res;}//分层static boolean bfs() {q.clear();q.add(1);Arrays.fill(deep, 0);cur = head.clone();//拷贝deep[1] = 1;while (!q.isEmpty()) {int a = q.poll();for (int i = head[a]; i != -1; i = edge[i].next) {if (deep[edge[i].to] == 0 && edge[i].val > 0) {q.add(edge[i].to);deep[edge[i].to] = deep[a] + 1;}}}return deep[n] != 0;}//构建图private static void add(int a, int b, int c) {edge[++edge_num] = new Edge(head[a], b, c);head[a] = edge_num;}static class Edge {int next;int to;int val;public Edge(int next, int to, int val) {this.next = next;this.to = to;this.val = val;}public Edge() {}}}

如果觉得《最大流问题模板-java-蓝桥杯-算法训练-网络流裸题》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。