网络流,huhangqi 都能看懂

定义

网络:一种特殊的有向图 (\(G=(V,E)\)),在一般的图中多了源点(s),汇点(t)和每一条边都有容量,指的是这条边能流过的最大的流量。

流量:\(f(x,y)\) 表示这条边已经流过了多少,\(f(x)\) 表示这个点往外流了多少,有 \(f(x)= \sum_{i} f(i,u) - \sum _i f(i,u)\)。这个流量是后期定义的,而不是原网络就有的。

s-t 割:\(\{S,T\}\) 为网络的一种划分,即 \(S\cup T = V,S\cap T = \varnothing\)\(s\in S ,t\in T\),那么称 \(\{S,T\}\) 为原网络的 s-t 割(以下简称割),割的容量定义为 \(||S,T||= \sum_{u\in S}\sum_{v\in T} c(u,v)\)

网络流问题主要解决的是以下问题及其衍生问题。

  • 最大流问题,找到合适的流,使得流最大的 \(f\)

  • 最小割问题,求得 \(\min||S,T||\)

  • 最小费用最大流,给每一条边设定一个费用,只要经过这一条边就需要花费指定的费用,使得达到最大流的时候费用最小。

最大流

给一个定义剩余容量,容量与容量之差叫做剩余容量 \(c_f(u,v) = c(u,v) - f(u,v)\)

然后对于所有剩余容量组成的网络定义为残量网络。
对于每一个 \(s\rightarrow t\) 的路径,我们成为增广路,而对于这一条路,我们填充流量的过程叫做增广。

在增广的过程中,我们需要建一个反向边,以便于我们退流。这条边的流量是 \(-f(u,v)\)

有一个比较 naive 的写法,直接每一次 BFS 从 \(s\rightarrow t\) 的路径,直接增广一下,在反向边退流。直到流量不在增加位置。

这个算法即为 EK 算法,时间复杂度是 \(O(|V||E|^2)\)。一般来说也跑不满。

BFS 分层:给点表上到源点的距离。

多路增广:如果增广一次,最短路没有变化,可以继续增广,直到最短路变大为止。bfs 之后,去掉一些可能在最短路上的边。只要沿着这些边增广即可。

当前弧优化:就是每一次遍历到一个点都是从上一次遍历完的边继续遍历。这样子显然更快。

优化之后即为 dinic 算法。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=10005,inf=1e9;
int n,m,s,t,las[N],cnt;
struct Edge {
	int v,nxt,now;
} e[N<<1];
void add(int u,int v,int w) {
	e[++cnt] = {v,las[u],w};
	las[u] = cnt;
	e[++cnt] = {u,las[v],0};
	las[v] = cnt;
}
namespace NF {
	int dep[N],cur[N];
	void init() {
		memset(las,-1,sizeof las),cnt=1;
	}
	bool bfs() {
		queue<int>q;
		q.push(s);
		memset(dep,0,sizeof dep);
		dep[s]=1;
		while(!q.empty()) {
			int u=q.front();
			q.pop();
			for(int i = las[u],v=e[i].v ; ~i; i=e[i].nxt,v=e[i].v) {
				if(!dep[v] && e[i].now )
					dep[v] = dep[u] +1,q.push(v);
			}
		}
		return dep[t];
	}
	int dfs(int u,int fl) {
		if(u==t || !fl)return fl;
		int res=0;
		for(int &i=cur[u],v=e[i].v ,d; ~i; i=e[i].nxt,v=e[i].v) {
			if(dep[v] == dep[u] +1 && (d = dfs(v,min(fl-res,e[i].now )))) {
				e[i].now -= d;
				e[i^1].now+=d;
				res +=d;
				if(res==fl)return res;
			}
		}
		return res;
	}
	long long dinic() {
		int maxflow=0;
		while(bfs()) {
			memcpy(cur, las, sizeof cur);
			maxflow += dfs(s, inf);
		}
		return maxflow;	
	}
};
signed main() {
	scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
	NF::init();
	for(int i=1,u,v,w; i<=m; i++) {
		scanf("%lld%lld%lld",&u,&v,&w);
		add(u,v,w);
	}
	cout<<NF::dinic()<<endl;
	return 0;
}

然后你发现这个时间复杂度是 \(O(|V|^2|E|)\)。看起来也没快多少。

但是在一般的随机图上,dinic 跑的特别快,在边权为 1 的图上甚至可以到 \(O(|E|\sqrt{ |V|}|)\)

实测大概速度差 8 倍。

知周所众,网络流默认不卡 dinic,所以就没学预流推进 useless。

最小割

我们知道最大流等于最小割。

\(F\) 为最大流, \(C\) 为最小割。证明 \(F=C\)

显然 \(F\ge C\) ,因为你对着最大流割一下,就能得到一个割。

对于任意一个割 \((S,T)\)

\(F = f(s,t) = \sum_{u\in S,v\in T,(u,v)\in E} f(u,v)-\sum_{u\in T,v\in S,(u,v)\in E} f(u,v) \le \sum_{u\in S,v\in T,(u,v)\in E} f(u,v) \le \sum _{u\in S,v\in T,(u,v)\in E} c(u,v) = C\)

综上,\(F=C\)

然后最小割直接通过最大流即可证明。

最小费用最大流

即费用流。

只需将 EK 算法或 Dinic 算法中找增广路的过程,替换为用最短路算法寻找单位费用最小的增广路即可。

称为 SSP 算法,不难,相信大家都会。

posted @ 2026-04-22 15:42  hnczy  阅读(19)  评论(2)    收藏  举报