网络流算法主要有两方面问题:
1、最大流
2、最小费用最大流
一、 最大流
https://www.cnblogs.com/ZJUT-jiangnan/p/3632525.html
先要理解一下反向边,然后以下是在他的基础上的三种方法
1、ff算法
dfs来找增广路
和下面代码其实是一样的
2、edmonds-Karp版本(EK算法)
用bfs来找增广路
import java.math.BigInteger;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
class Main{
public static void main(String[] args) {
ek();
}
static int ek(int s,int t){
int new_flow = 0;
int max_flow = 0;
while(new_flow!=0){
new_flow = find_path_bfs(s,t);
update_residual_network(t,new_flow);
max_flow+= new_flow;
}
return max_flow;
}
static int max = 10;
static int INF = Integer.MAX_VALUE;
static int m;
static int [][]map;
static int[] visited = new int[max];
static int[] pre = new int[max];
static int find_path_bfs(int s,int t){
visited = new int[max];
pre = new int[-1];
visited[s] = 1;
int min = INF;
Queue<Integer> q = new LinkedList<>();
((LinkedList<Integer>) q).push(s);
while (!q.isEmpty()){
int cur = q.poll();
if(cur==t){
break;
}
for(int i = 1;i<=m;i++){
if(visited[i]==0 && map[cur][i]!=0){
((LinkedList<Integer>) q).push(i);
min = Math.min(min,map[cur][i]);
pre[i] = cur;
visited[i] = 1;
}
}
}
if(pre[t]==-1) return 0; //他前面没有??
return min;
}
static int update_residual_network(int u,int flow){
while (pre[u]!=-1){
map[pre[u]][u]-=flow;
map[u][pre[u]]+=flow;
u = pre[u];
}
}
}
3、dinic算法
根据残量网络计算层次图。
在层次图中使用DFS进行增广直到不存在增广路
重复以上步骤直到无法增广
参考:https://mp.weixin.qq.com/s/XlX1_JazHdMbX5-ToCGmgg
import java.util.*;
public class fourth {
static int n;
static int m;
static int s;
static int t;
static int[][] map = new int[10009][10009];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
s = sc.nextInt();
t= sc.nextInt();
for(int i = 1;i<=m;i++){
int u,v,w;
u = sc.nextInt();
v = sc.nextInt();
w = sc.nextInt();
add(u,v,w);
}
System.out.println(dinic(s,t));
}
static int INF = Integer.MAX_VALUE;
static int[] visited= new int[10009];
static List<H> v[] = new LinkedList[10009];
static void init(){
for(int i= 0;i<v.length;i++){
v[i] = new LinkedList<H>();
}
}
static class H{
int to,cap,rev;
public H(int to, int cap, int rev) {
this.to = to;
this.cap = cap;
this.rev = rev;
}
}
static void add(int f,int t ,int c){
v[f].add(new H(t,c,v[t].size()));//而且是f add t。size
v[t].add(new H(f,0,v[f].size()-1));
}
static int dinic(int s,int t){
int flow = 0;
while (bfs(s,t)){
while (true){
int d = dfs(s,t,Integer.MAX_VALUE);
if(d==0) break;
flow+= d;
}
}
return flow;
}
static int[] dep = new int[10009];
static boolean bfs(int s,int t){
init();
for (int i = 0;i<dep.length;i++){
dep[i] = -1;
}
Queue<Integer> que = new LinkedList<>();
while (!que.isEmpty()) que.poll(); //清空一下
((LinkedList<Integer>) que).add(s);
dep[s] = 0;
while (!que.isEmpty()){
int k =((LinkedList<Integer>) que).pollFirst();
for(int i = 0;i<v[k].size();i++){
H node = v[k].get(i);
if(dep[node.to]==-1 && node.cap>0){
dep[node.to] =dep[k]+1;
((LinkedList<Integer>) que).add(node.to);
}
}
}
return dep[t]!=-1;
}
static int dfs(int s,int t,int f){
int d = 0;
if(s ==t) return f;
for(int i = 0;i<v.length;i++){
H node = v[s].get(i);
if((node.cap>0) && (dep[node.to] == dep[s]+1)){
d = dfs(node.to,t,Math.min(f,node.cap));
if (d!=0){
v[s].get(i).cap-=d;
v[node.to].get(node.rev).cap+=d;
return d;
}
}
}
return 0;
}
}
这里有一个小小的应用是二分图匹配,可以用dinic做,但是并不是很快,所以可以用匈牙利算法来做 原文章 https://mp.weixin.qq.com/s/9Koj65-zLZLtAk8h28nrWw
Sample Input
6 3 3
1 1
1 2
1 3
2 1
2 3
3 1
0
Sample Output
3
第一、増广是怎么走的呢,是从一个未匹配点出发 然后非匹配边 匹配边 非匹配边 一直这么交错着走的,最后一条边没有参与匹配,并且始点和终点还没 有被选择过.这样交错进行,
如果代码有看不懂可以参考这个文章,带进去想一下 https://mp.weixin.qq.com/s/HT6_GGPXexQ6eYjVdKlQLw?client=tim&ADUIN=1691439978&ADSESSION=1560154671&ADTAG=CLIENT.QQ.5603_.0&ADPUBNO=26882
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e3+10;
int n1,n2,k;
//n1,n2为二分图的顶点集,其中x属于n1,y属于n2
int map[maxn][maxn],vis[maxn],link[maxn];
//link记录n2中的点y在n1中所匹配的x点的编号
void init()
{
memset(map,0,sizeof map);
memset(link,0,sizeof link);
}
int find(int x)
{
for(int i=1;i<=n2;i++)
{
if(map[x][i]&&!vis[i])//x->i有边,且节点i未被搜索
{
vis[i]=1;//标记节点已经被搜索
//如果i不属于前一个匹配M,或被i匹配到的节点可以寻找到增广路
if(link[i]==0||find(link[i]))
{
link[i]=x;//更新
return 1;//匹配成功
}
}
}
return 0;
}
int main()
{
while(~scanf("%d",&k)&&k){
scanf("%d%d",&n1,&n2);
init();
for(int i=0;i<k;i++)
{
int x,y;
scanf("%d%d",&x,&y);
map[x][y]=1;
}
int ans=0;
for(int i=1;i<=n1;i++)
{
memset(vis,0,sizeof vis);
if(find(i)) ans++;
}
printf("%d\n",ans);
}
return 0;
}
二、 最小费用最大流
就是把bfs换成spfa
最短路问题:基本内容是:若网络中的每条边都有一个数值(长度、成本、时间等),则找出两节点(通常是源节点和阱节点)之间总权和最小的路径就是最短路问题
先写一下spfa算法
【初始化】
dis数组全部赋值为INF,pre数组全部赋值为-1(表示还不知道前驱),
dis[s] = 0 表示源点不要求最短路径(或者最短路径就是0)。
【队列+松弛操作】
读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队,这样不断从队列中取出顶点来进行松弛操作。
以此循环,直到队空为止就完成了单源最短路的求解。
【算法过程】
设立一个队列用来保存待优化的顶点,优化时每次取出队首顶点 u,并且用 u 点当前的最短路径估计值dis[u]对与 u 点邻接的顶点 v 进行松弛操作,如果 v 点的最短路径估计值dis[v]可以更小,且 v 点不在当前的队列中,就将 v 点放入队尾。这样不断从队列中取出顶点来进行松弛操作,直至队列空为止。
【检测负权回路】
方法:如果某个点进入队列的次数大于等于 n,则存在负权回路,其中 n 为图的顶点数。
说明:SPFA无法处理带负环的图。(X)
注意::这个算法中的visited有一点点不一样是这个点出了队列以后把它标称未访问,之后还可以再把它加进来,在普通的bfs中就是第一个点变成visited加进队列,之后遍历上下左右,但凡满足要求的就加进队列,都附成visited 。这里要注意一下
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;
public class first {
static int vertex_num;
static int edge_num;
static int source;
static int []visited = new int[100];
static int []enqueue_num = new int[100]; //记录入队次数
static int dist[] = new int[100]; //源点到顶点i的最短距离
static int path[] = new int[100]; //记录最短路的路径
static int matrix[][] = new int[100][100];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
vertex_num = sc.nextInt(); //开始先输入顶点数,边数,源点
edge_num = sc.nextInt();
source = sc.nextInt();
for(int i = 0;i<vertex_num;i++){
for(int j = 0;j<vertex_num;j++){
matrix[i][j] =Integer.MAX_VALUE;
}
}
for(int i = 1;i<edge_num;i++){
int u = sc.nextInt();
int v = sc.nextInt();
int w = sc.nextInt();
matrix[u][v] =w;
}
if(SPFA()){
Print();
}else{
System.out.println("这个图有负权环,不能用spfa");
}
}
static boolean SPFA(){
visited = new int[100];
enqueue_num = new int[100];
for(int i =0;i<vertex_num;i++){
dist[i] = Integer.MAX_VALUE;
path[i] =source;
}
Queue<Integer> q = new LinkedList<Integer>();
((LinkedList<Integer>) q).push(source);
dist[source] = 0;
visited[source] = 1;
enqueue_num[source]++;
while (!q.isEmpty()){
int u = q.poll();
visited[u] = 0;
for(int v = 0;v<vertex_num;v++){
if(matrix[u][v] !=Integer.MAX_VALUE){
if(dist[u]+matrix[u][v]<dist[v]){
dist[v] = dist[u]+matrix[u][v];
path[v] = u;
if(visited[v]==0){
((LinkedList<Integer>) q).add(v);
enqueue_num[v]++;
if(enqueue_num[v]>=vertex_num){
return false;
}
visited[v] = 1;
}
}
}
}
}
return true;
}
static void Print(){
for(int i = 0;i<vertex_num;i++){
if(i!=source){
int p = i;
Stack<Integer> s = new Stack<>();
System.out.print("顶点:"+source+"到顶点:"+p+"的最短路径是:");
while (source!=p){
s.push(p);
p = path[p];
}
System.out.print(source);
while (!s.isEmpty()){
System.out.print("--"+s.peek());
s.pop();
}
System.out.println("最短路径是"+dist[i]);
}
}
}
}
写完spfa后就来写一下最大流最小费用吧
例题:https://www.luogu.org/problemnew/show/P3381#sub
参考:https://www.luogu.org/blog/21679/p3381-mu-ban-zui-xiao-fei-yong-zui-tai-liu
我的代码:(X)
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Scanner;
public class fifth {
static int n;
static int m;
static int s;
static int t;
static int flowd;
static int costed;
static int maxn = 5002;
static int inf = Integer.MAX_VALUE;
static List<edge> ed = new LinkedList<>();
static List<Integer> g[]= new LinkedList[maxn]; //g数组里每一个都是一个List
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
s = sc.nextInt();
t= sc.nextInt();
for(int i = 1;i<=m;i++){
int q1,q2,q3,q4;
int u = -1;
q1 = sc.nextInt();
q2 =sc.nextInt();
q3 = sc.nextInt();
q4= sc.nextInt();
ed.add(new edge(q1,q2,q3,0,q4));
g[q1].add(++u);
ed.add(new edge(q2,q1,0,0,-q4));
g[q2].add(++u);
}
flowd = 0;
costed = 0;
while (bf());
System.out.println(flowd+" "+costed);
}
static boolean bf(){
for(int i = 1;i<=n;i++) d[i] = inf;
Queue<Integer> h = new LinkedList();
((LinkedList) h).add(s);
while (!h.isEmpty()){ //bfs
int x = h.poll();
inq[x] = false;
for(int i = 0;i<g[x].size();i++){
edge e = ed.get(g[x].get(i));
if(e.cap>e.flow && d[e.to]>d[x]+e.cost){
d[e.to] = d[x]+e.cost;
p[e.to] = g[x].get(i);//操作的是边的标号,可以把它放到edge用get取出
a[e.to] =Math.min(a[x],e.cap-e.flow);
if(!inq[e.to]){
((LinkedList<Integer>) h).add(e.to);
inq[e.to] = true;
}
}
}
}
if(d[t] ==inf) return false;
flowd+= a[t];
costed+=a[t]*d[t];
int u = t;
while (u!=s){
ed.get(p[u]).flow+=a[t];
ed.get(p[u]^1).flow-=a[t];
u = ed.get(p[u]).from;
}
return true;
}
static class edge{
int from,to,cap,flow,cost;
public edge(int from, int to, int cap, int flow, int cost) {
this.from = from;
this.to = to;
this.cap = cap;
this.flow = flow;
this.cost = cost;
}
}
List<edge> list = new LinkedList<>();
static boolean inq[] = new boolean[maxn];
static int d[] = new int[maxn]; //到目前为止最小费用
static int p[]= new int[maxn];//记录上一条边(递归用)
static int a[] = new int[maxn];//到当前为止的最大流量
}