网络流

学长讲着讲着就听不懂了,妈妈捏。

60 页讲义、

文章省去了大量的证明(?


定义 \(f[u\to v]\)\(u\to v\) 的 ⌈ 流量 ⌋, \(c[u\to v]\)\(u\to v\) 的 ⌈ 流量限制 ⌋,\(s\) 为源点,\(t\) 为汇点。

合法流的充要条件
  • 反对称性 \(f[u\to v]=-f[v\to u]\)

  • 流量守恒 对于 \(u \neq s,t, \sum_{a} f[u\to a]=0\)

  • 容量限制 \(f[u\to v]\leq c[u\to v]\)


最大流

找到满足条件的 \(f\)\(\sum_a f[s\to a]\) 最大的,这是 ⌈ 最大流 ⌋。模型中允许出现环流,但是环流不影响。

\(r[u\to v]=c[u\to v]-f[u\to v]\),我们称 \(r\) 为 ⌈ 残量网络 ⌋ 。

残量调整定理
  • 原网络的所有合法流可以在残量网络上调整而获得。意思是不管怎么改都不会让一些合法的流失去。可以反悔(?(((

\(flow(f)\) 表示流 \(f\) 的流量 \(\sum f[u\to t]\)

对于当前残量网络,求一个流方案 \(f\) 满足 \(flow(f)>0\),将残量网络减去 \(f\) 并将答案加上 \(flow(f)\)。重复这一过程不断迭代,最大流不断变大 & 最大流必然有限,很合理喵 >w<

一条 \(s\to t\) 的路径中所有 \(r>0\),这条路径是一条 ⌈ 增广路 ⌋ 。取 \(\min\{r\}\) 在路径上每条边流去,这个过程叫 ⌈ 增广 ⌋。

增广路定理
  • 当前流是最大流,当且仅当当前残量网络上找不出增广路。意思是不断找增广即可得到最大流。

引用 yyc blog 的一段证明口胡 /bx/bx

流的数值是 \(flow\),假如有更大的流,至少有一个 \(f[s,u]\) 会变大。相应的,至少会有一个 \(f[u,s]\) 变小,破坏了 \(u\) 的能量守恒。为了 \(u\) 出入相当,存在 \(f[u,v]\) 变大,这里 \(v\neq s\)

以此类推,不守恒会传递下去,如果形成一个圈,流量总和不变,仍然不合法。如果传到 \(t\) 合法了,就是一条 kawaii 的增广路。


Ford-Fulkerson

用 dfs 找增广路,找到一条就增广。复杂度上界 \(O(maxflow*m)\)。上界是单位流量都搜一次。


Edmonds–Karp

只考虑容量为正的边,用 bfs 找增广路。

最短路单调定理
  • 在 EK 中,源点到各点的最短路不降。增广路的长度单调增,至多有 O(n) 种。

在增广路长度一定的增广中:每次增广至少使得一条边的容量变为 0,且新加入的反向边无用。最多 \(m\) 次增广,每次 bfs 的复杂度为 \(O(m)\)。复杂度为 \(O(m^2)\)

那么总的复杂度就是 \(O(nm^2)\) 啦 >w<(((

常数优化的话,bfs 到 \(t\) 就可以停止,某个点不发出流量可以直接从最短路 DAG 里面剔除。

#include<bits/stdc++.h>
#define int long long
#define N 2010
#define M 50010

using namespace std;

const int inf=1ll<<60;
int hd[N], to[M], eg[M], nxt[M], tot=1;
int v[N], incf[N], pre[N], n, m, s, t, flow;
queue<int> q;

void eadd(int u,int v,int w) {
	to[++tot]=v, eg[tot]=w;
	nxt[tot]=hd[u], hd[u]=tot;
}

bool bfs() {
	for(int i=1; i<=n; ++i) v[i]=0;
	while(q.size()) q.pop();
	q.push(s), v[s]=1, incf[s]=inf; 
	while(q.size()) {
		int x=q.front();
		q.pop();
		for(int i=hd[x]; i; i=nxt[i])
			if(eg[i]) {
				int y=to[i];
				if(v[y]) continue;
				incf[y]=min(incf[x],eg[i]);
				pre[y]=i, v[y]=1, q.push(y);
				if(y==t) return 1;
			}
	}
	return 0;
}

void upt() {
	int x=t;
	while(x!=s) {
		int i=pre[x];
		eg[i]-=incf[t];
		eg[i^1]+=incf[t];
		x=to[i^1];
	}
	flow+=incf[t];
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m >> s >> t;
	while(m--) {
		int u, v, w;
		cin >> u >> v >> w;
		eadd(u,v,w), eadd(v,u,0);
	}
	while(bfs()) upt();
	cout << flow << endl;
	return 0;
}

Dinic

多路增广。只考虑在最短路上面的边,bfs 处理。

dfs 进行多路增广。到达 \(u\) 点时,枚举各个出边搜索看看能流出多少。如果能流,减去流走的流量,调整残量网络,尝试下一条边。最后返回总的流出去的流量。之后不断退栈直到可以。复杂度 \(O(n^2m)\)

当前弧优化

记录点第 \([1,cur]\) 条出边流不出流量,枚举从 \(cur+1\) 开始。不加此优化,复杂度退化。

#include<bits/stdc++.h>
#define int long long
#define N 2010
#define M 50010

using namespace std;

const int inf=1ll<<60;
int hd[N], to[M], eg[M], nxt[M], cur[N], tot=1;
int dis[N], incf[N], pre[N], n, m, s, t, maxflow, fl;
queue<int> q;

void eadd(int u,int v,int w) {
	to[++tot]=v, eg[tot]=w;
	nxt[tot]=hd[u], hd[u]=tot;
}

bool bfs() {
	for(int i=1; i<=n; ++i) dis[i]=0;
	while(q.size()) q.pop();
	q.push(s), dis[s]=1;
	while(q.size()) {
		int x=q.front(); q.pop();
		for(int i=hd[x]; i; i=nxt[i]) {
			int y=to[i], val=eg[i];
			if(val&&!dis[y]) {
				dis[y]=dis[x]+1;
				q.push(y);
				if(y==t) return 1;
			}
		}
	}
	return 0;
}

int dfs(int x,int flow) {
	if(x==t) return flow;
	int rest=flow;
	for(int i=cur[x]; i&&rest; i=nxt[i]) {
		cur[x]=i;
		int y=to[i], val=eg[i];
		if(val&&dis[x]+1==dis[y]) {
			int k=dfs(y,min(rest,val));
			if(!k) dis[y]=0;
			eg[i]-=k, eg[i^1]+=k, rest-=k;
		}
	}
	return flow-rest;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m >> s >> t;
	while(m--) {
		int u, v, w;
		cin >> u >> v >> w;
		eadd(u,v,w), eadd(v,u,0);
	}
	while(bfs()) {
		for(int i=1; i<=n; ++i)
			cur[i]=hd[i];
		while(fl=dfs(s,inf)) maxflow+=fl;
	} 
	cout << maxflow << endl;
	return 0;
}

费用流

在最大流问题的基础上,边加上每单位流量的费用 \(l[u\to v]\)。要求在最大流的基础上总费用最小。

费用具有对称性,\(l[u\to v]=-l[v\to u]\)。与最大流不同的,费用流的重边需要分别处理。

  • 在流一定的前提下费用最小当且仅当残量网络上不存在费用的负环。

SSP

每次找出任意一个环流,若为非负环,将其去除,这样 \(cost(f)\) 不会变大。最终一定能找出负环。(demo 初始图中无负环时一定不会有负环。负环必然与增广路有交。

Edmonds Karp

每次增广用 SPFA 求解最短路。

复杂度 \(O(nm^2c)\),模仿 dinic 就是 \(O(n^2mc)\)

#include<bits/stdc++.h>
#define int long long
#define N 5010
#define M 100010

using namespace std;

const int inf=1ll<<60;
int hd[N], to[M], eg[M], nxt[M], lv[M], tot=1;
int incf[N], pre[N], n, m, s, t, flow, cost, dis[N];
queue<int> q;

void eadd(int u,int v,int w,int l) {
	to[++tot]=v, eg[tot]=w, lv[tot]=l; 
	nxt[tot]=hd[u], hd[u]=tot;
}

bool spfa() {
	for(int i=1; i<=n; ++i) dis[i]=inf;
	while(q.size()) q.pop();
	dis[s]=0, q.push(s), incf[s]=inf; 
	bool flag=0;
	while(q.size()) {
		int x=q.front(); q.pop();
		for(int i=hd[x]; i; i=nxt[i])
			if(eg[i]) {
				int y=to[i], v=lv[i];
				if(dis[x]+v<dis[y]) {
					dis[y]=dis[x]+v;
					incf[y]=min(incf[x],eg[i]);
					pre[y]=i, q.push(y);
					flag|=(y==t);
				}
			}
	}
	return flag;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m >> s >> t;
	while(m--) {
		int u, v, w, l;
		cin >> u >> v >> w >> l;
		eadd(u,v,w,l), eadd(v,u,0,-l);
	}
	while(spfa()) {
		int x=t;
		while(x!=s) {
			int i=pre[x];
			eg[i]-=incf[t];
			eg[i^1]+=incf[t];
			x=to[i^1];
		}
		flow+=incf[t];
		cost+=dis[t]*incf[t];
	}
	cout << flow << " " << cost << endl;
	return 0;
}

变种(?(((((

费用流的凸性

\(f(k)\) 表示流 \(k\) 的流量时所需的最小费用,则 \(\delta f(k)=f(k)-f(k-1)\)
是第 \(k\) 单位流量增广时残量网络上的最短路。由 \(\Delta f (k)\) 单调递增,\(f(k)\) 下凸。


最大费用任意流

不要求流量最大,只要求费用最大。
使用 SSP 算法,根据最短路单调定理,不断增广直到最长路为负。


正费用最大流

使用 SSP 算法,总费用是上凸的,当费用要变负时不增广。


无源汇上下界可行流

流量除了上界也设立下界。

额我开摆了,以后再来学这些吧


最小割

posted @ 2023-08-12 22:58  Hypoxia571  阅读(18)  评论(0编辑  收藏  举报