なんでバカのブログを読みたいの!为什么要看菜鸟的博客!|

园龄:粉丝:关注:

网络流 笔记

本文原在 2024-07-22 10:17 发布于本人洛谷博客。

一、定义与性质

1. 基本定义

从水厂出发,有很多节点和水管,节点不能存水,但容量无限,水管有容量上限,全部水管最终经过某些节点都会流向某个工厂里,问最多同时能给工厂发多少水?

流网络:这张图。

源点:水厂。

汇点:工厂。

弧:水管。

弧的流量:这条水管当前流多少水,对于弧 \((u,v)\)\(f(u,v)\) 表示。

弧的容量:这条水管最多能流多少水,用 \(c(u,v)\) 表示。

弧的残量:这条水管还能流多少水,即 \(c(u,v)-f(u,v)\)

流量网络/容量网络/残量网络:边权表示/流量/容量/残量的流网络。

(重要) 增广路:在残量网络中,还能从源点流向汇点的一条路径。

2. 性质

  • 斜对称性:\(f(u,v)=-f(v,u)\)

  • 流量守恒:

    • 对于节点,输出量和输入量相等。
    • 源点的输出量等于汇点的输入量。

二、最大流

1. EK 算法

第一章第 1 节中所提到的问题就属于最大流问题。

基本思路就是:建一张容量网络,找到一条路径就给每条边减去相应的流量,并在剩下的残量网络上,不断找增广路。

但是对于下面这个图,显然找不到增广路了,但是肉眼可见最大流是 \(1\to 2\to 4\) 加上 \(1\to 3\to 4\)

因此给程序一个反悔的机会:给每条边都建一条反向边(根据 \(0\oplus 1 =1\)\(1\oplus 0 = 0\)\(2\oplus 1 = 3\)\(3\oplus 1 = 2\)\(4\oplus 1 =5\)……的性质,可以用 \(i\)\(i\oplus 1\) 作为一组相反的边),边权表示正向边的流量,实时更新。

每次找到一条路径后,给路径上的边减去本路径的最大流,路径上的反边加上本路径的最大流。这样就会发现:如果路径中含有一条反向边,它恰好能将正向边的容量给推回去。

int bfs() { // bfs寻找增广路
for (int i = 1; i <= n; i++)
vis[i]=0;
queue<int> q;
q.push(s);
vis[s] = 1;
dis[s] = oo;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[x]; i; i = edge[i].next) {
if (edge[i].flow == 0)
continue;
// 如果是正向边:没有残量了怎么流?如果是反向边:正向边没有流量怎么流?
int v = edge[i].v;
if (vis[v])
continue; // 访问过了
flow[v] = min(flow[u], edge[i].flow);
// 显然,一条路径的最大流取决于能流最小的那条边
pre[v] = i; // 记录前驱,方便修改边权
q.push(v);
vis[v] = 1;
if (v == t)
return 1; // 找到了一条增广路
}
}
return 0;
}
void update() { // 更新所经过边的正向边权以及反向边权
int u = t;
while (u != s) {
int v = pre[u];
edge[v].flow -= flow[t];
edge[v ^ 1].flow += flow[t];
u = edge[v ^ 1].v;
}
maxflow += flow[t]; // 累加每一条增广路经的最小流量值
}
void EK() {
while (bfs())
update();
}

2. Dinic 算法

EK 算法一次才找一条增广路,使用 Dinic 算法的 DFS 可以一次寻找多条增广路。

用 BFS 对图分层,作用在于一次可以得到多条长度相同的最短增广路。

每次从当前点出发,选用从当前点所在层到下一层的边,发送一定的流量,流量的大小取边残量和当前点从源点获取的剩余流中两者的最小值。

搜索完成后,返回一个流量值,即这条增广路的流量,此时就能够对边和反向边的残量进行更新了。

bool bfs() {
fill(dis, dis + N, oo);
queue<int> q;
q.push(s);
dis[s] = 0;
now[s] = head[s];
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (edge[i].w <= 0 or dis[v] != oo)
continue;
q.push(v);
now[v] = head[v]; // 记录当前弧优化,可以不写,直接用 head
dis[v] = dis[u] + 1; // 分层
if (v == t) // 到汇点了
return true;
}
}
return false;
}
int dfs(int u, int last) {
if (u == t) return last; // 到汇点了
int ret = 0;
for (int i = now[u]; i and last; i = edge[i].next) {
now[u] = i;
int v = edge[i].v;
if (edge[i].w <= 0 or dis[v] != dis[u] + 1) // 没有流量或者层数对不上
continue;
int tmp = dfs(v, min(last, edge[i].w)); // 往后走的最大流
if (!tmp) dis[v] = oo;
edge[i].w -= tmp;
edge[i ^ 1].w += tmp;
ret += tmp;
last -= tmp;
}
return ret;
}
void dinic() {
while(bfs())
ans += dfs(s, oo);
}

三、费用流

以最小费用最大流为例:

每条水管要收费,假设水管 \((u,v)\) 的单价是 \(cost\),那么你就要付 \(f(u,v)\times cost\)

将 EK 算法的 BFS 改成 SPFA 即可,可能有负边不能 Dijkstra。

namespace McMf {
int cost[N], flow[N], pre[N];
bool vis[N];
bool spfa() {
queue<int> q;
fill(cost, cost + N, oo);
memset(vis, 0, sizeof vis);
q.push(s);
cost[s] = 0;
vis[s] = true;
flow[s] = oo;
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int i = head[u]; i; i = edge[i].next) {
if (!edge[i].flow)
continue;
int v = edge[i].v;
if (cost[v] > cost[u] + edge[i].cost) {
cost[v] = cost[u] + edge[i].cost;
flow[v] = min(flow[u], edge[i].flow);
pre[v] = i;
if (!vis[v]) {
q.push(v);
vis[v] = true;
}
}
}
}
return cost[t] <= oo / 2;
}
void mcmf() {
while (spfa()) {
int u = t;
maxflow += flow[u];
mincost += flow[u] * cost[u];
while (u != s) {
int p = pre[u];
edge[p].flow -= flow[t];
edge[p ^ 1].flow += flow[t];
u = edge[p ^ 1].v;
}
}
}
}
using namespace McMf;

四、上下界网络流

1. 无源汇上下界可行流

假设上下界是 \([L,R]\),点 \(x\) 流入的量是 \(in_x\),流出的量是 \(out_x\)

令每条边都先流自己的 \(L\),统计到 \(in_x\)\(out_x\) 中,然后构造一个流网络,边权为 \(R-L\)

如果 \(in_x>out_x\),那么建边 \((s,x,in_x-out_x)\)

如果 \(in_x<out_x\),那么建边 \((x,t,out_x-in_x)\)

在这个新构造的流网络中,如果源点流出的每一条边都能流满,那么说明流量平衡。

2. 有源汇上下界可行流

连接题目给出的源点 \((t,s,[0,\infty])\),用 1 的方法。

3. 有源汇上下界最大流

跑完可行流后,在残量网络中找 \(s\to t\) 的最大流。

#include <bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;
const int N = 1e6 + 10, oo = 1e18;
int n, m, s, t, s1, t1, in[N], out[N], over;
int head[N], ide = 1;
struct EDGE {
int v, next, w;
} edge[N];
void add(int u, int v, int w) {
edge[++ide] = {v, head[u], w};
head[u] = ide;
edge[++ide] = {u, head[v], 0};
head[v] = ide;
}
int dis[N], now[N];
bool bfs() {
fill(dis, dis + N, oo);
queue<int> q;
q.push(s);
dis[s] = 0;
now[s] = head[s];
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (edge[i].w <= 0 or dis[v] != oo)
continue;
q.push(v);
now[v] = head[v];
dis[v] = dis[u] + 1;
if (v == t)
return true;
}
}
return false;
}
int dfs(int u, int last) {
if (u == t)
return last;
int ret = 0;
for (int i = now[u]; i and last; i = edge[i].next) {
now[u] = i;
int v = edge[i].v;
if (edge[i].w <= 0 or dis[v] != dis[u] + 1)
continue;
int tmp = dfs(v, min(last, edge[i].w));
if (!tmp)
dis[v] = oo;
edge[i].w -= tmp;
edge[i ^ 1].w += tmp;
ret += tmp;
last -= tmp;
}
return ret;
}
int dinic() {
int ret = 0;
while(bfs())
ret += dfs(s, oo);
return ret;
}
signed main() {
IOS;
cin >> n >> m >> s1 >> t1;
s = n + 1, t = n + 2;
for (int i = 1, u, v, lb, ub; i <= m; i++) {
cin >> u >> v >> lb >> ub;
in[v] += lb, out[u] += lb;
add(u, v, ub - lb);
}
for (int i = 1; i <= n + 2; i++)
if (in[i] > out[i]) {
add(s, i, in[i] - out[i]);
over += in[i] - out[i];
} else
add(i, t, out[i] - in[i]);
add(t1, s1, oo);
if (dinic() != over) {
cout << "please go home to sleep";
return 0;
}
s = s1, t = t1;
cout << dinic();
return 0;
}

4. 有源汇上下界最小流

求出可行流,把 \((t,s,[0,\infty])\) 删掉,再跑一次,前后两次相减。

本文作者:Garbage fish's Blog

本文链接:https://www.cnblogs.com/Garbage-fish/p/18709926

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Garbage_fish  阅读(7)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起