网络流复习笔记
网络流复习笔记
前言
不建议作为学习文章,没有教程,仅为个人的复习笔记。
Dinic
还会写。时间复杂度的理论上界为 \(O(n^2m)\),但实际很快(何况还有弧优化)。
如果所有的边流量均为 \(1\),时间复杂度为 \(O(\min(n^{\frac{2}{3}},m^{\frac{1}{2}})m)\)。
如果图为二分图,时间复杂度为 \(O(n^{\frac{1}{2}}m)\)。
最大流
创建超级源点和汇点,跑 Dinic 算法即可。
例题:Luogu P3376 【模板】网络最大流
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define inf 0x3f3f3f3f3f3f3f3fll
int n, m, s, t;
int firs[205], nex[10005], to[10005], w[10005], tot = 1, x, y, z;
int cur[205], dep[205], Ansflow;
bool inque[205];
queue < int > q;
void Add (int u, int v, int k){
++ tot;
nex[tot] = firs[u];
firs[u] = tot;
to[tot] = v;
w[tot] = k;
}
bool Bfs (){
for (int i = 1;i <= n;++ i){
dep[i] = inf;
inque[i] = false;
cur[i] = firs[i];
}
while (! q.empty ())
q.pop ();
dep[s] = 0;
inque[s] = true;
q.push (s);
while (! q.empty ()){
int u = q.front ();
q.pop ();
inque[u] = false;
for (int e = firs[u];e;e = nex[e]){
int v = to[e];
if (dep[v] > dep[u] + 1 && w[e] != 0){
dep[v] = dep[u] + 1;
if (inque[v] == false){
q.push (v);
inque[v] = true;
}
}
}
}
return dep[t] != inf;
}
int Dfs (int u, int flow){
int rlow = 0;
if (u == t){
Ansflow += flow;
return flow;
}
int used = 0;
for (int e = cur[u];e;e = nex[e]){
cur[u] = e;
int v = to[e];
if (w[e] != 0 && dep[v] == dep[u] + 1){
rlow = Dfs (v, min (flow - used, w[e]));
if (rlow != 0){
used += rlow;
w[e] -= rlow;
w[e ^ 1] += rlow;
if (used == flow)
break;
}
}
}
return used;
}
int Dinic (){
while (Bfs () == true)
Dfs (s, inf);
return Ansflow;
}
signed main (){
scanf ("%lld%lld%lld%lld", &n, &m, &s, &t);
for (int i = 1;i <= m;++ i){
scanf ("%lld%lld%lld", &x, &y, &z);
Add (y, x, 0);
Add (x, y, z);
}
printf ("%lld\n", Dinic ());
return 0;
}
最小割
根据最大流最小割定理(max flow/min cut theory):对于任意一个只有一个源和一个汇的图来说,从源到汇的最大流等于最小割。
遇到求最小割的题目,求最大流即可。
最小化割边个数
把所有的边流量改成 \(1\) ,再求最小割即可。
在满足最小割的前提下最小化割边数量
先求出最小割,把没有满流的边容量改成 \(\infty\) ,满流的边的容量改成 \(1\) ,重新跑一边最小割就可以求出最小割边数量。
经典模型(来自 OI-wiki)
“二者选其一”
有 \(n\) 个物品和两个集合 \(A,B\),如果一个物品没有放入 \(A\) 集合会花费 \(a_i\),没有放入 \(B\) 集合会花费 \(b_i\);有若干形如 \(u_i,v_i,w_i\) 的限制条件,表示如果 \(u_i\) 和 \(v_i\) 不在一个集合,会花费 \(w_i\)。每个物品必须且只能属于一个集合,求最小的代价。
设置源点 \(s\) 和汇点 \(t\),对应 \(A\) 集合和 \(B\) 集合。\(s\) 向 \(i\) 连容量为 \(a_i\) 的边,\(i\) 向 \(t\) 连容量为 \(b_i\) 的边。对于 \(u,v,w\),在 \(u\) 和 \(v\) 之间连容量为 \(w_i\) 的双向边(后文求最小割时,这条边看作一条)。
而这时最小割可以将其划分到 \(A\) 或 \(B\) 二者其一中。
所以最小割就是最小代价。
最大权值闭合图
给定一张有向图,每个点都有一个权值(可以为正或负或 \(0\),你需要选择一个权值和最大的子图,使得子图中每个点的后继都在子图中。
设置源点 \(s\) 和汇点 \(t\)。若 \(u\) 点权值非负,则 \(s\) 向 \(u\) 连边,流量为该点点权;否则 \(u\) 向 \(t\) 连边,流量为该点点权的相反数。
对于每条在原图中的边 \(u \to v\),\(u\) 向 \(v\) 连边,流量为 \(\infty\)。
因为最小割的割边只能是满流的边,所以原图最小割割边只能与 \(s\) 或 \(t\) 相连,故最小割所割出的与 \(s\) 相连的点在原图所对应的子图一定满足闭合的条件。
其次因为最小割,所以所有正权值的点的和减去最小割就是答案。
最小费用最大流(Min Cost - Max Flow)
把 Dinic 算法中找增广路的过程替换为找可行最短路,即可。
例题:Luogu P3381 【模板】最小费用最大流
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define inf 0x3f3f3f3f3f3f3f3fll
int n, m, s, t;
int firs[5005], nex[100005], to[100005], w[100005], c[100005], tot = 1;
int dis[5005], pre[5005], cur[5005], Ansflow, Mincost;
bool inque[5005];
queue < int > q;
void Add (int u, int v, int x, int y){
++ tot;
nex[tot] = firs[u];
firs[u] = tot;
to[tot] = v;
w[tot] = x;
c[tot] = y;
}
bool Bfs (){
for (int i = 1;i <= n;++ i){
dis[i] = inf;
pre[i] = 0;
cur[i] = inf;
inque[i] = false;
}
while (! q.empty ())
q.pop ();
q.push (s);
inque[s] = true;
dis[s] = 0;
while (! q.empty ()){
int u = q.front ();
q.pop ();
inque[u] = false;
for (int e = firs[u];e;e = nex[e]){
int v = to[e];
if (w[e] != 0 && dis[v] > dis[u] + c[e]){
dis[v] = dis[u] + c[e];
pre[v] = e;
cur[v] = min (w[e], cur[u]);
if (inque[v] == false)
q.push (v);
inque[v] = true;
}
}
}
return dis[t] != inf;
}
void MFMC (){
while (Bfs () == true){
int now = t;
while (now != s){
w[pre[now]] -= cur[t];
w[pre[now] ^ 1] += cur[t];
now = to[pre[now] ^ 1];
}
Ansflow += cur[t];
Mincost += cur[t] * dis[t];
}
}
signed main (){
scanf ("%lld%lld%lld%lld", &n, &m, &s, &t);
for (int i = 1;i <= m;++ i){
int A, B, C, D;
scanf ("%lld%lld%lld%lld", &A, &B, &C, &D);
Add (A, B, C, D);
Add (B, A, 0, - D);
}
MFMC ();
printf ("%lld %lld\n", Ansflow, Mincost);
return 0;
}
上下界网络流
无源汇上下界可行流
对于每条边给出一个流量区间表示这条边的最小可流量和最大可流量。判断是否存在一种流量方式使流量平衡,如果有,求出一个可行流。
可以将原图拆成一个下界网络和一个差网络。在差网络中建立源点 \(s'\) 和汇点 \(t'\),设一个点 \(i\) 在下界网络中入流量和出流量的差的绝对值为 \(y_i\) ,对于在下界网络中一个入流量大于出流量的点 \(u\),在差网络中由 \(s'\) 向 \(u\) 连一条流量为 \(y_u\) 的附加边;否则如果出流量大于入流量,则在差网络中由 \(u\) 向 \(t'\) 连一条流量为 \(y_u\) 的附加边。
这时可以发现的是,我们相当于把下界网络中的额外流量放入差网络中,而新下界网络流量平衡。这时我们跑一遍最大流,即可得到一个流量平衡的流量方式,如果新差网络中的所有附加边都满流,则有可行流;否则没有。
可以发现的是,实际的操作中我们不需要建出下界网络,也只用判断连着 \(s'\) 或 \(t'\) 的所有附加边是否均满流即可。
有源汇上下界可行流
汇点向源点连一条 \([0,\infty]\) 的边,即转化为无源汇上下界可行流。
有源汇上下界最大流
先求出一个有源汇上下界可行流,然后在差网络的残量网络上再求最大流,最大流+可行流即为答案。
因为我们求出的可行流和最大流的流量都平衡,所以相加后流量平衡。
残量网络上的源汇点为 \(s,t\) ,不为 \(s',t'\)。
例题:Luogu P5192 Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流
Code
#include <bits/stdc++.h>
using namespace std;
#define Fin puts (""); Clear (); return ;
const int inf = 5e8;
int n, m, rest[2005];
int cnt, las, S, T, s1, t1, s2, t2, needflow, Ansflow, flow1, flow2;
int firs[2005], nex[200005], to[200005], w[200005], tot = 1;
void Clear (){
for (int i = 1;i <= cnt;++ i)
firs[i] = rest[i] = 0;
for (int i = 1;i <= tot;++ i)
nex[i] = to[i] = w[i] = 0;
S = T = s1 = t1 = s2 = t2 = 0;
needflow = Ansflow = 0;
flow1 = flow2 = 0;
cnt = las = 0;
tot = 1;
}
void Add (int u, int v, int l, int r){
rest[u] -= l;
rest[v] += l;
++ tot;
nex[tot] = firs[u];
firs[u] = tot;
to[tot] = v;
w[tot] = r - l;
++ tot;
nex[tot] = firs[v];
firs[v] = tot;
to[tot] = u;
w[tot] = 0;
}
int id_1 (int i){
return i;
}
int id_2 (int i){
return i + n;
}
int dep[2005], cur[2005];
bool inq[2005];
queue < int > Q;
bool Bfs (){
for (int i = 1;i <= las;++ i){
dep[i] = inf;
cur[i] = firs[i];
inq[i] = false;
}
dep[S] = 0;
Q.push (S);
inq[S] = true;
while (! Q.empty ()){
int u = Q.front ();
Q.pop ();
inq[u] = false;
for (int e = firs[u];e;e = nex[e]){
int v = to[e];
if (dep[v] > dep[u] + 1 && w[e] > 0){
dep[v] = dep[u] + 1;
if (! inq[v])
Q.push (v);
inq[v] = true;
}
}
}
return dep[T] != inf;
}
int Solve (int u, int flow){
if (u == T){
Ansflow += flow;
return flow;
}
int used = 0, rlow;
for (int e = cur[u];e;e = nex[e]){
cur[u] = e;
int v = to[e];
if (dep[v] == dep[u] + 1 && w[e] > 0){
rlow = Solve (v, min (flow - used, w[e]));
if (rlow != 0){
used += rlow;
w[e] -= rlow;
w[e ^ 1] += rlow;
if (used == flow)
break;
}
}
}
return used;
}
int Dinic (){
Ansflow = 0;
while (Bfs ())
Solve (S, inf);
return Ansflow;
}
void Action (){
s1 = id_2 (m) + 1;
t1 = s1 + 1;
s2 = t1 + 1;
t2 = s2 + 1;
cnt = t2;
Add (t1, s1, 0, inf);
for (int i = 1;i <= m;++ i){
int x;
scanf ("%d", &x);
Add (id_2 (i), t1, x, inf);
}
for (int i = 1;i <= n;++ i){
int c, d, T, L, R;
scanf ("%d%d", &c, &d);
Add (s1, id_1 (i), 0, d);
for (int j = 1;j <= c;++ j){
scanf ("%d%d%d", &T, &L, &R);
Add (id_1 (i), id_2 (T + 1), L, R);
}
}
{
for (int i = 1;i <= t1;++ i)
if (rest[i] > 0){
Add (s2, i, 0, rest[i]);
needflow += rest[i];
} else
if (rest[i] < 0)
Add (i, t2, 0, - rest[i]);
S = s2;
T = t2;
las = t2;
if (Dinic () != needflow){
puts ("-1");
Fin
}
flow1 = w[3];
}
{
w[2] = w[3] = 0;
for (int i = 5;i <= tot;i += 2)
w[i] = 0;
S = s1;
T = t1;
las = t1;
flow2 = Dinic ();
}
printf ("%d\n", flow1 + flow2);
Fin
}
int main (){
while (scanf ("%d%d", &n, &m) != EOF)
Action ();
return 0;
}
有源汇上下界最小流
先求出一个有源汇上下界可行流,从汇点到源点跑一遍最大流,这时因为由 \(t\) 退回的流量最大,所以剩余流量最小,可行流-汇点向源点最大流即为答案。
因为我们求出的可行流和最大流的流量都平衡,所以相减后流量平衡。
残量网络上的源汇点为 \(s,t\) ,不为 \(s',t'\)。
有/无源汇有上下界最小费用可行流
与有/无源汇有上下界可行流同理,只是附加边的费用为 \(0\)。
本文作者:saubguiu
本文链接:https://www.cnblogs.com/imcaigou/p/17884369.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步