并不会从零开始讲网络流 , 并且其中很多是个人理解.


\(①\) \(:\) 最大流 \(\cdot\) \(DK\)

每次去找 一条 (注意是一条) 路增广 , 再去更新.

\(vis\) 保证每次每个点只找到一次 , 也防止双向边成环卡死.

一定不能用 \(flow\) 单个变量去记录最小流 , 要用数组 \(flow\) 去更新 , 因为在一条路的路程中会产生最小流 , 但这条路不一定走得通.


关于反向边的建立 , 我认为就是让能流的残余流量流完

浅谈网络流_最大流

很显然答案是 : 198.

但若我们走的不是 "显然" 的路 , 走 \(1 \rightarrow 2 \rightarrow 3 \rightarrow 4\) , 则会变成 :

浅谈网络流_ios_02

这一次我们只能走 \(1 \rightarrow 3 \rightarrow 2 \rightarrow 4\) , 则会变成 :

浅谈网络流_ios_03

三个图总体来看发现 : \(2 - 3\) 反向边的建立让我们两条显然的路都经过了流量 \(1\) .


#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 201;
const int M = 5001;
const int inf = 2147483647;
long long ans;
int n,m,s,t,cnt = 1;
int head[N],vis[N],pre[N],flow[N];
struct bian{
	int to,v,next;
}len[M<<1];
void add(int from,int to,int v){
	len[++cnt].v = v;
	len[cnt].to = to;
	len[cnt].next = head[from];
	head[from] = cnt;
}
bool bfs(){
	queue<int> Q;
	flow[s] = inf;
	memset(vis,0,sizeof(vis));
	Q.push(s) , vis[s] = 1;
	while(Q.size()){
		int now = Q.front();Q.pop();
		for(int k=head[now];k;k=len[k].next){
			int to = len[k].to , v = len[k].v;
			if(!vis[to]&&v){
				//flow = min(flow,v);
				flow[to] = min(v,flow[now]);
				pre[to] = k;
				Q.push(to),vis[to] = 1;
				if(to == t) return true;
			}
		}
	}
	return false;
}
void updata(){
	int x = t;
	while(x != s){
		int id = pre[x];
		len[id].v -= flow[t];
		len[id^1].v += flow[t];
		x = len[id^1].to;
	}
	ans += flow[t];
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;++i){
		int x,y,v;scanf("%d%d%d",&x,&y,&v);
		add(x,y,v),add(y,x,0);
	}
	while(bfs()) updata();
	printf("%lld",ans);
	return 0;
}

\(②\) \(:\) 最大流 \(\cdot\) \(Dinic\)

分层数组 \(dep\) : 保证能多路增广 , 且防止双向边成环卡死.

如图 : \(1 \rightarrow 2 \rightarrow 4\) , \(1 \rightarrow 3 \rightarrow 4\) , 两条路能在一次 \(dfs\) 中完成增广 , 而不是像 \(DK\) 那次被 \(vis\) 数组限定每次只能选一条.

浅谈网络流_#include_04


当前弧优化 \(now\) 数组 : 当一条路进行了增广后 , 我们没必要在同一次 \(dfs\) 中对其再增广 , 不好解释看图吧.

浅谈网络流_ios_05

当我们走了 \(1 \rightarrow 2 \rightarrow 4 \rightarrow 6\) , \(1 \rightarrow 2 \rightarrow 4 \rightarrow 5\) 时 ,

下一次就只需走 \(1 \rightarrow 3 \rightarrow 4 \rightarrow 7\) 即可 , 从 \(6\) , \(5\) 走一定会遇到 边权为 \(0\) 边.

注意我们用 &k = now[x] 进行对 \(now\) 数组的实时更新.


小技巧优化 : nowflow < flow , nowflow += nodeflow.

结合图与递归就能理解了 :

浅谈网络流_最大流_06


#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 201;
const int M = 5001;
const int inf = 2147482647;
long long ans;
int n,m,s,t,cnt = 1;
int head[N],now[N],dep[N];
struct bian{
	int to,next,v;
}len[M<<1];
void add(int from,int to,int v){
	len[++cnt].v = v;
	len[cnt].to = to;
	len[cnt].next = head[from];
	head[from] = cnt;
}
bool bfs(){
	queue<int> Q;
	memset(dep,0,sizeof(dep));
	Q.push(s) , dep[s] = 1;
	while(Q.size()){
		int pos = Q.front();Q.pop();
		for(int k=head[pos];k;k=len[k].next){
			int to = len[k].to , v = len[k].v;
			if(!dep[to]&&v){
				dep[to] = dep[pos] + 1;
				Q.push(to);
				if(to == t) return true;
			}
		}
	}
	return false;
}
int dfs(int x,int flow){
	if(x == t) return flow;
	int nowflow = 0;
	for(int &k=now[x];k&&nowflow<flow;k=len[k].next){
		int to = len[k].to , v = len[k].v;
		if(dep[to]==dep[x]+1&&v){
			int nodeflow = dfs(to,min(v,flow-nowflow));
			if(nodeflow > 0){
				len[k].v -= nodeflow;
				len[k^1].v += nodeflow;
				nowflow += nodeflow;
			} 
		}
	}
	return nowflow;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;++i){
		int x,y,v;scanf("%d%d%d",&x,&y,&v);
		add(x,y,v),add(y,x,0);
	}
	while(bfs()){
		for(int i=1;i<=n;++i)
			now[i] = head[i];
		while(int f = dfs(s,inf))
			ans += f;
	}
	printf("%lld",ans);
	return 0;
}

\(③\) \(:\) 最小费用最大流 \(\cdot\) \(DK\)

在每次我们找残余网络的基础上 (v != 0) , 加入对最短路的判断 .

同时注意我们这里的 \(vis\) 数组在数据弹出时要 \(vis = 0\) , 这在最大流 \(\cdot\) \(DK\) 里面是没有的 , 这里利用的是 \(spfa\) 更新最短路的原理 , 当前有效状态更新并非最优 . 利用 dis[to] > dis[x] + cost 不会陷入双向边成环卡死的现象.