网络流基础 复习笔记
尚未全部完成。无零基础内容。
目录
-
模板代码
-
一些坑点
-
定理套路
-
题目分析
模板代码
1. 网络最大流
P3376 【模板】网络最大流
用于解决求一个网络最大流问题(废话)。下面使用了效率较高且实现简单的 Dinic 算法。
而且貌似我并没有加「当前弧优化」也能 AC ?
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#define Maxn 10010
#define Maxm 100010
using namespace std;
const int INF = 0x3f3f3f;
int read() {
int x = 0,f = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1;
c = getchar();
}
while('0' <= c && c <= '9') {
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
int N,M,S,T;
struct Edge {
int next,to,dis;
}
edge[2 * Maxm];
int head[Maxn],edge_num = 1;
void add_edge(int from,int to,int dis) {
edge[++edge_num].next = head[from];
edge[edge_num].to = to;
edge[edge_num].dis = dis;
head[from] = edge_num;
}
queue <int> Q;
int dist[Maxn];
bool bfs() {
memset(dist,0,sizeof(dist));
while(!Q.empty()) Q.pop();
Q.push(S); dist[S] = 1;
while(!Q.empty()) {
int u = Q.front();
Q.pop();
for(int i = head[u];i;i = edge[i].next) {
int v = edge[i].to;
if(!dist[v] && edge[i].dis) {
dist[v] = dist[u] + 1;
Q.push(v);
if(v == T) return 1;
}
}
}
return 0;
}
int dinic(int u,int flow) {
if(u == T) return flow;
int rest = flow;
for(int i = head[u];i && rest;i = edge[i].next) {
int v = edge[i].to;
if(dist[u] + 1 == dist[v] && edge[i].dis) {
int k = dinic(v,min(rest,edge[i].dis));
if(!k) dist[v] = 0;
rest -= k;
edge[i].dis -= k;
edge[i ^ 1].dis += k;
}
}
return flow - rest;
}
int main() {
N = read(); M = read();
S = read(); T = read();
int u,v,w;
for(int i = 1; i <= M; i++) {
u = read(); v = read(); w = read();
add_edge(u,v,w);
add_edge(v,u,0);
}
int maxflow = 0, flow = 0;
while(bfs()) {
flow = dinic(S,INF);
maxflow += flow;
}
cout << maxflow;
return 0;
}
除此之外还有 EK 算法,由于效率较低再次不再赘述。但是 EK 算法也有他的用武之地。费用流的实现一般就会建立在 EK 的基础上。如下:
2. 最小费用最大流
P3381 【模板】最小费用最大流
用于解决在 最大流的基础上,使得费用最小的问题(废话)。
设图的边集为 \(E\),某条边 \(e\) 的流量为 \(f_e\),费用为 \(c_e\),则整个图的费用定义为 \(cost(G)=\sum_{e \in E } f_e \times c_e\)。
下面是用的为 EK 算法。在最大流 EK 模板的基础上,将 bfs 部分改为 SPFA(不用 Dij 的原因是因为可能有负权)即可求解最小费用最大流。最大费用最大流同理。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#define Maxn 5010
#define Maxm 50010
using namespace std;
int N, M, S, T, maxflow, ans;
int read() {
int x = 0,f = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1;
c = getchar();
}
while('0' <= c && c <= '9') {
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
struct Edge {
int next, to;
int dis, cost;
}
edge[Maxm * 2];
int head[Maxn], edge_num = 1;
void add_edge(int from, int to, int dis, int cost) {
edge[++edge_num].next = head[from];
edge[edge_num].to = to;
edge[edge_num].dis = dis;
edge[edge_num].cost = cost;
head[from] = edge_num;
}
int dist[Maxn], incf[Maxn], pre[Maxn];
bool inq[Maxn];
void init() {
memset(dist, 63, sizeof(dist));
memset(inq, 0, sizeof(inq));
memset(incf, 63, sizeof(incf));
memset(pre, 0, sizeof(pre));
}
bool SPFA() {
bool flag = 0;
queue <int> Q;
init();
Q.push(S); inq[S] = 1; dist[S] = 0;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = 0;
for(int i = head[u]; i; i = edge[i].next) {
if(!edge[i].dis) continue;
int v = edge[i].to;
if(dist[v] > dist[u] + edge[i].cost) {
dist[v] = dist[u] + edge[i].cost;
incf[v] = min(incf[u], edge[i].dis);
if(v == T) flag = 1;
pre[v] = i;
if(!inq[v]) {inq[v] = 1; Q.push(v);}
}
}
}
return flag;
}
void upd() {
int x = T;
while(x != S) {
int i = pre[x];
edge[i].dis -= incf[T];
edge[i ^ 1].dis += incf[T];
x = edge[i ^ 1].to;
}
maxflow += incf[T];
ans += dist[T] * incf[T];
}
int main() {
N = read(); M = read(); S = read(); T = read();
int u, v, c, w;
for(int i = 1; i <= M; i++) {
u = read(); v = read(); c = read(); w = read();
add_edge(u, v, c, w);
add_edge(v, u, 0, -w);
}
while(SPFA()) upd();
cout << maxflow << " " << ans << endl;
return 0;
}
此外还有「有上下界网络流问题」。由于笔者太菜(暴露了)这里暂时不提。
一些坑点
此处以 Dinic 算法为例(因为最常用)。
这个人怎么犯这么多傻逼错误啊 ?
-
由于要处理反向边,初始时要令
edge_num为 \(1\)。 -
每次 bfs 前,
dist数组归零。 -
这句话
int k = dinic(v,min(rest,edge[i].dis));其中的
rest一定不要写成flow!(脑残几次了)
套路定理
比较常用的两个定理,很菜是吧。
-
最大流最小割定理
众所周知,最大流 = 最小割,感性可证(?)。
-
最小路径覆盖 = 点数 - 最大流
请注意,此处所谓 最大流 的建图并非原图。详见模板题 P2764 最小路径覆盖问题。
网络流的关键在于建模。以下是几个套路:
-
拆点 如果遇到这样的字眼,比如「只能经过某个点 \(x\) 次」之类的,想到拆点。把一个点拆成 \(2\) 个点,中间用一条有流量的边连接。
-
分组 其实这个有点涉及到二分图了。遇到「选出尽量多的元素,但某些元素之间不能共存」之类的问题,可以想到 最大点权独立集:
先将所有元素分为互不相交的两组。建立超级源汇点 \(S,T\), 把 \(S\) 和 \(T\) 分别与两组中每个点相连,流量全为 \(1\) ; 再将互不相容的点相连,流全为 \(INF\)。假设一开始我们选取了全部元素,现在必须要删除一些元素。显然删除元素的个数即为该网络的 最小割,亦即最大流。
例题:hdu1569 方格取数。
-
灵感建模 反正看到有关匹配「匹配」的字眼,可以考虑网络流。
题目分析
先咕着

浙公网安备 33010602011771号