网络流
学长讲着讲着就听不懂了,妈妈捏。
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 算法,总费用是上凸的,当费用要变负时不增广。
无源汇上下界可行流
流量除了上界也设立下界。
额我开摆了,以后再来学这些吧