最大流
概述
我们有一张图,要求从源点流向汇点的最大流量(可以有很多条路到达汇点),就是我们的最大流问题。
最大流算法有很多种,基本上分为两类:
1.“增广路”算法。例如 Edmonds-Karp 算法、Dinic 算法、ISAP 算法。
2.“预流推进”算法。例如
Ford-Fulkerson 增广路方法
残留网络:
迭代后残留容量所产生的图,每次新的迭代在上一次的残留网络上进行。
增广路:
在残留网络上找到一条从 s 到 t 的路径。
Ford-Fulkerson 方法的运行时间依赖于增广路径的搜索次数。虽然用 BFS 或者 DFS 都行,但是 DFS 这种深度搜索模式可能陷入长时间的迭代,但如果用 BFS,几次就够了。
EK 算法
时间复杂度 ,一般能处理
~
规模的网络
用 BFS 来计算增广路径,就是 Edmonds-Karp 算法。
// 模板题(https://www.luogu.com.cn/problem/P3376)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 201;
long long graph[maxn][maxn]; // 邻接矩阵存图
int n, m, s, t, pre[maxn];
long long flow[maxn];
int bfs() {
memset(flow, 0, sizeof flow);
queue<int> q; q.push(s);
pre[s] = -1, flow[s] = LLONG_MAX;
while(!q.empty()) {
int tp = q.front(); q.pop();
if (flow[t]) continue;
for(int i = 1; i <= n; ++ i) {
if (flow[i] || !graph[tp][i]) continue;
flow[i] = min(flow[tp], graph[tp][i]);
pre[i] = tp;
q.push(i);
}
}
return flow[t];
}
int main() {
scanf("%d%d%d%d", &n, &m, &s, &t);
memset(graph, 0, sizeof graph);
for(int i = 1; i <= m; ++ i) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
graph[u][v] += w;
}
long long maxflow = 0;
while(bfs()) { // 每次bfsd都能找到一条增广路
long long used = flow[t];
for(int i = t; ~pre[i]; i = pre[i]) {
graph[ pre[i] ][i] -= used;
graph[i][ pre[i] ] += used;
}
maxflow += used;
}
printf("%lld\n", maxflow);
return 0;
}
Dinic 算法
时间复杂度 ,一般能够处理
~
规模的网络
Dinic 算法 的过程是这样的:每次增广前,我们先用 BFS 来将图分层。如果不存在到汇点的增广路(即汇点的层数不存在),我们即可停止增广,并且确保我们找到的增广路是最短的。
Dinic 算法有几个优化:
- 多路增广 :每次找到一条增广路的时候,如果残余流量没有用完怎么办呢?我们可以利用残余部分流量,再找出一条增广路。这样就可以在一次 DFS 中找出多条增广路,大大提高了算法的效率。
- 当前弧优化 :如果一条边已经被增广过,那么它就没有可能被增广第二次。那么,我们下一次进行增广的时候,就可以不必再走那些已经被增广过的边。
- 剪枝优化,有效代替当前弧优化,当一个点的流量跑满时,可把 d[x] 标记为 0,dfs 不再遍历该点。
- 路径优化,把从 s 向 t 分层,改为 从 t 向 s 分层,可以省去很多多余步骤。
相较于EK算法,显然Dinic算法的效率更优也更快:虽然在稀疏图中区别不明显,但在稠密图中Dinic的优势便凸显出来了(所以Dinic算法用的更多),此外,Dinic算法求解二分图最大匹配的时间复杂度为
// 模板题(https://www.luogu.com.cn/problem/P3376)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 201;
const int maxm = 5001;
struct edge{
long long to, next, cap, flow; // 容量 和 流量
}e[maxm << 1]; int head[maxn], tot;
int n, m, s, t, d[maxn], cur[maxn]; // d[]分层,cur[]弧优化
void addedge(int u, int v, int w) {
e[++tot] = {v, head[u], w, 0}, head[u] = tot;
e[++tot] = {u, head[v], 0, 0}, head[v] = tot;
}
bool bfs() { // 分层 t-s路径优化
memset(d, 0x3f, sizeof d);
queue<int> q; q.push(t);
d[t] = 0;
while(!q.empty()) {
int x = q.front(); q.pop();
for(int i = head[x]; ~i; i = e[i].next) {
int j = e[i].to;
if (d[j] == d[0] && e[i^1].cap > e[i^1].flow) {
d[j] = d[x] + 1;
if (j == s) return true;
q.push(j);
}
}
}
return false;
}
long long dfs(int x, long long flow, long long used = 0) {
if (x == t) return flow;
// for(int i = cur[x]; ~i && flow != used; i = e[ cur[x] = i ].next) { // 当前弧优化
for(int i = head[x]; ~i && flow != used; i = e[i].next) {
long long j = e[i].to, f;
if (d[x] - 1 == d[j] && e[i].cap > e[i].flow) {
f = dfs(j, min(e[i].cap-e[i].flow, flow-used));
if (f) e[i].flow += f, e[i^1].flow -= f, used += f;
}
}
if (!used) d[x] = 0; // 剪枝优化
return used;
}
int main() {
scanf("%d%d%d%d", &n, &m, &s, &t);
memset(head, -1, sizeof head); tot = -1;
for(int i = 1; i <= m; ++ i) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
}
long long maxflow = 0;
while(bfs()) { // 每次dfs分层都能找到多条增广路
// memcpy(cur, head, sizeof head);
maxflow += dfs(s, INT_MAX);
}
printf("%lld\n", maxflow);
return 0;
}
ISAP 算法
时间复杂度
ISAP算法,就是在Dinic算法上,进行了进一步优化,大概也是常见的最快的增广路算法了。
ISAP算法在这几个方面进行了大优化:
- 引进了gap数组,优化了断层,一定程度上减少了不必要的时间损失
- 你只需要一次bfs即可
当我们从终点向起点跑完bfs之后,就再也不需要重跑了。当我们遍历完一个节点,居然还剩下了一些流,那么我们只有将它的高度提高,才有可能在下一次遍历中把流推向其他边。不过这个算法最大的遗憾还是这张图:
很显然,按照ISAP算法,S的高度会不断提高8次才能开始从1增广。如果数据再强力一些,就有可能卡掉。
// 模板题(https://www.luogu.com.cn/problem/P3376)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 201;
const int maxm = 5001;
struct node {
long long to, next, cap, flow;
}e[maxm << 1]; int head[maxn], tot;
int n, m, s, t, d[maxn], cur[maxn];
int gap[maxn]; // GAP优化
int bfs() {
memset(d, 0, sizeof d);
memset(gap, 0, sizeof gap);
queue<int> q; q.push(t);
gap[ d[t] = 1 ] = 1;
while(!q.empty()) {
int x = q.front(); q.pop();
for(int i = head[x]; ~i; i = e[i].next) {
int j = e[i].to;
if (!d[j] && e[i^1].cap > e[i^1].flow) {
gap[ d[j] = d[x] + 1 ] ++;
q.push(j);
}
}
}
return 0;
}
long long dfs(int x, long long flow) {
if(x == t) return flow;
long long used = 0;
for(int &i = cur[x]; ~i; i = e[i].next) { // 弧优化
int j = e[i].to;
if (d[x] - 1 == d[j] && e[i].cap > e[i].flow) {
long long low = dfs(j, min(e[i].cap-e[i].flow, flow-used));
if (low) e[i].flow += low, e[i^1].flow -= low, used += low;
}
if (used == flow) return used;
}
(--gap[ d[x] ]) ? (++gap[ ++d[x] ]) : (d[s] = n+1); // gap优化
return used;
}
int main() {
scanf("%d%d%d%d", &n, &m, &s, &t);
memset(head, -1, sizeof head), tot = -1;
for(int i = 1; i <= m; ++ i) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
e[++tot] = {v, head[u], w, 0}, head[u] = tot;
e[++tot] = {u, head[v], 0, 0}, head[v] = tot;
}
long long maxflow = bfs(); // 一次反向dfs分层
while(d[s] <= n) { // dfs若出现断层则退出
memcpy(cur, head, sizeof head);
maxflow += dfs(s, LLONG_MAX);
}
printf("%lld\n", maxflow);
return 0;
}
预流推进
// 板子题 (https://www.luogu.com.cn/problem/P4722)
#pragma GCC optimize(3, "Ofast", "inline")
#pragma GCC target("avx,avx2,fma")
#pragma GCC optimization ("unroll-loops")
#include<bits/stdc++.h>
#define getchar() (S == T && (T = (S = BB) + fread(BB, 1, 1 << 15, stdin), S == T) ? EOF : * S ++)
char BB[1 << 20], *S = BB, *T = BB;
inline long long read() {
char ch = getchar(); long long f = 1, x = 0;
for(; ch>'9'||ch<'0'; ch=getchar()) if(ch=='-')f=-1;
for(;ch<='9'&&ch>='0';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
return x * f;
}
const int maxn = 1e5+1;
const int INF(INT_MAX);
struct edge {
int to,flow,next;
edge(int to,int flow,int next):to(to),flow(flow),next(next) {}
};
std::vector<edge>a[maxn];
std::vector<int>list[maxn],h,cnt,que,e;
typedef std::list<int> List;
std::vector<List::iterator>iter;
List dlist[maxn+100];
typedef std::vector<edge>::iterator Iterator;
int hst,nowh;
void init(int n) {
hst=0, nowh=0;
h.clear(), cnt.clear(), que.clear(), e.clear();
for(int i=1; i<=n; i++) a[i].clear();
}
inline void addEdge(const int u,const int v,const int f) {
a[u].push_back(edge(v,f,a[v].size()));
a[v].push_back(edge(u,0,a[u].size()-1));
}
inline void relabel(int n,int t) {
h.assign(n,n);
h[t]=0;
cnt.assign(n,0);
que.clear();
que.resize(n+1);
int qh=0,qt=0;
for(que[qt++]=t; qh<qt;) {
int u=que[qh++],het=h[u]+1;
for(Iterator p=a[u].begin(); p!=a[u].end(); ++p) {
if(h[p->to]==n&&a[p->to][p->next].flow>0) {
cnt[h[p->to]=het]++;
que[qt++]=p->to;
}
}
}
for(register int i=0; i<=n; ++i) {
list[i].clear();
dlist[i].clear();
}
for(register int u=0; u<n; ++u) {
if(h[u]<n) {
iter[u]=dlist[h[u]].insert(dlist[h[u]].begin(),u);
if(e[u]>0)
list[h[u]].push_back(u);
}
}
hst=(nowh=h[que[qt-1]]);
}
inline void push(int u,edge &ed) {
int v=ed.to;
int df=std::min(e[u],ed.flow);
ed.flow-=df;
a[v][ed.next].flow+=df;
e[u]-=df;
e[v]+=df;
if(0<e[v]&&e[v]<=df)
list[h[v]].push_back(v);
}
inline void push(int n,int u) {
int nh=n;
for(Iterator p=a[u].begin(); p!=a[u].end(); ++p) {
if(p->flow>0) {
if(h[u]==h[p->to]+1) {
push(u,*p);
if(e[u]==0)
return;
} else
nh=std::min(nh,h[p->to]+1);
}
}
int het=h[u];
if(cnt[het]==1) {
for(register int i=het; i<=hst; ++i) {
for(List::iterator it=dlist[i].begin(); it!=dlist[i].end(); ++it) {
cnt[h[*it]]--;
h[*it]=n;
}
dlist[i].clear();
}
hst=het-1;
} else {
cnt[het]--;
iter[u]=dlist[het].erase(iter[u]);
h[u]=nh;
if(nh==n)
return;
cnt[nh]++;
iter[u]=dlist[nh].insert(dlist[nh].begin(),u);
hst=std::max(hst,nowh=nh);
list[nh].push_back(u);
}
}
inline int hlpp(int n,int s,int t) {
if(s==t)
return 0;
nowh=0;
hst=0;
h.assign(n,0);
h[s]=n;
iter.resize(n);
for(register int i=0; i<n; ++i)
if(i!=s)
iter[i]=dlist[h[i]].insert(dlist[h[i]].begin(),i);
cnt.assign(n,0);
cnt[0]=n-1;
e.assign(n,0);
e[s]=INF;
e[t]=-INF;
for(register int i=0; i<(int)a[s].size(); ++i)
push(s,a[s][i]);
relabel(n,t);
for(int u; nowh>=0;) {
if(list[nowh].empty()) {
nowh--;
continue;
}
u=list[nowh].back();
list[nowh].pop_back();
push(n,u);
}
return e[t]+INF;
}
int main() {
int n = read(), m = read(), s = read(), t = read();
init(n);
for(int i = 1; i <= m; ++ i) {
int u = read(), v = read(), w = read();
addEdge(u, v, w);
}
printf("%d\n",hlpp(n+1,s,t));
return 0;
}
/*
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
7 14 1 7
1 2 5
1 3 6
1 4 5
2 3 2
2 5 3
3 2 2
3 4 3
3 5 3
3 6 7
4 6 5
5 6 1
6 5 1
5 7 8
6 7 7
10 16 1 2
1 3 2
1 4 2
5 2 2
6 2 2
3 5 1
3 6 1
4 5 1
4 6 1
1 7 2147483647
9 2 2147483647
7 8 2147483647
10 9 2147483647
8 5 2
8 6 2
3 10 2
4 10 2
答案:50 14 8
*/
关于acm竞赛图论的个人笔记
查看22道真题和解析
