网络流复习
网络流复习
网络流24题
首先是 Dinic 算法。核心是 EK 的 bfs 提前跑好,在分层图上找增广路。重点两个优化:废点优化和当前弧优化。
P2756 飞行员配对方案问题
此题是最大匹配的模板,可以使用匈牙利算法解决。
也可以这样建图:\(S \rightarrow 1 \cdots m, m+1 \cdots n \rightarrow T\),以及给出的合作关系,全部建流量为 \(1\) 的边,跑最大流即可。输出方案就是看各中间层的正向边是否流量剩余 \(0\),是的话当前边就已经选用了。
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
const int M = N * N;
const int inf = 0x3f3f3f3f;
int n, m, edt, S, T;
int head[N], to[M], nx[M], ew[M];
int d[N], cur[N];
void adde(int u, int v, int w) {
to[edt] = v, ew[edt] = w, nx[edt] = head[u], head[u] = edt++;
}
void Adde(int u, int v, int w) {
adde(u, v, w), adde(v, u, 0);
}
int q[N], hh, tt;
int bfs() {
memset(d, -1, sizeof(d));
hh = tt = 0;
d[q[tt++] = S] = 0, cur[S] = head[S];
while(hh < tt) {
int u = q[hh++];
for(int ed = head[u]; ~ed; ed = nx[ed]) {
int v = to[ed];
if(d[v] == -1 && ew[ed]) {
d[q[tt++] = v] = d[u] + 1, cur[v] = head[v];
if(v == T) return 1;
}
}
}
return 0;
}
int dfs(int u, int li) {
if(u == T) return li;
int flow = 0;
for(int ed = cur[u]; ~ed && flow < li; ed = nx[ed]) {
cur[u] = ed;
int v = to[ed];
if(d[v] == d[u] + 1 && ew[ed]) {
int t = dfs(v, min(ew[ed], li - flow));
// printf("%d -> %d flow : %d\n", u, v, t);
if(!t) d[v] = -1;
flow += t, ew[ed] -= t, ew[ed ^ 1] += t;
}
}
return flow;
}
int dinic() {
int t, flow = 0;
while(bfs()) while(t = dfs(S, inf)) flow += t;
return flow;
}
int main() {
scanf("%d%d", &m, &n);
memset(head, -1, sizeof(head));
S = n + 1, T = n + 2;
for(int i = 1; i <= m; i++) Adde(S, i, 1);
for(int i = m + 1; i <= n; i++) Adde(i, T, 1);
for(int u, v; ; ) {
scanf("%d%d", &u, &v);
if(u == -1 && v == -1) break;
Adde(u, v, 1);
}
printf("%d\n", dinic());
// for(int i = 0; i < edt; i++) printf("test: %d %d %d\n", to[i ^ 1], to[i], ew[i]);
for(int i = 0; i < edt; i += 2) {
if(ew[i] == 0) {
int u = to[i], v = to[i ^ 1];
if(u > v) swap(u, v);
if(u == S || v == T || u == T || v == S) continue;
printf("%d %d\n", u, v);
}
}
return 0;
}
接下来是 ISAP。ISAP 是将 Dinic 继续优化的算法,核心就是不用跑多次 bfs 了,直接开始时 bfs 从 \(T\) 开始去找 \(S\),中途把每个点的层级弄清楚。dfs 的时候如果一个点入流没有往后流满,这个点的当前层级就废了,直接层级加 \(1\)。\(S\) 的层级大于 \(n\) 时就结束。
可以使用当前弧优化但是不能废点了。可以加入很重要的 gap 优化,即统计每层的点数,如果某层无点则必然无增广路,直接结束。
P2762 太空飞行计划问题
最小割的模板。先假设所有实验都进行,收益为 \(\sum p_j\)。 建立 \(S \rightarrow E_j, W = p_j\),割掉这条边表示不做 \(E_j\) 实验了,代价 \(p_j\);建立 \(I_k \rightarrow T, W = c_k\),割掉这条边表示买 \(I_k\),花费 \(c_k\)。还有 \(E_j\) 向所有它的依赖仪器连边 \(W = + \infty\),表示想做这个实验就一定要买所有需要的仪器。求最小割即可。
最小割 = 最大流,直接用 ISAP 求最大流。求方案就是从 \(S\) 开始走未满流的所有边和 \(S\) 在最小割中相连,再推一下结果就行。
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
const int M = N * N;
const int inf = 0x3f3f3f3f;
int n, m, S = N - 2, T = N - 1;
int edt, head[N], to[M], nx[M], ew[M];
int d[N], cur[N], gap[N + 1];
int q[N], hh, tt;
int sump;
void adde(int u, int v, int w) {
to[edt] = v, ew[edt] = w, nx[edt] = head[u], head[u] = edt++;
}
void Adde(int u, int v, int w) {
// printf("%d -> %d ew: %d\n", u, v, w);
adde(u, v, w), adde(v, u, 0);
}
void get_tools(int u) { // experiment u
char tools[10000];
memset(tools,0,sizeof tools);
cin.getline(tools,10000);
int ulen=0,tool;
while (sscanf(tools+ulen,"%d",&tool)==1)
{
Adde(u, m + tool, inf);
if (tool==0)
ulen++;
else {
while (tool) {
tool/=10;
ulen++;
}
}
ulen++;
}
}
void bfs() {
memset(d, -1, sizeof(d));
memset(gap, 0, sizeof(gap));
hh = tt = 0;
gap[d[q[tt++] = T] = 0]++;
while(hh < tt) {
int u = q[hh++];
for(int ed = head[u]; ~ed; ed = nx[ed]) {
int v = to[ed];
if(d[v] == -1) gap[d[q[tt++] = v] = d[u] + 1]++;
}
}
}
int dfs(int u, int li) {
if(u == T) return li;
int flow = 0;
for(int ed = cur[u]; ~ed; ed = nx[ed]) {
cur[u] = ed;
int v = to[ed];
if(d[u] == d[v] + 1 && ew[ed]) {
int t = dfs(v, min(ew[ed], li - flow));
// printf("%d %d: t = %d\n", u, v, t);
flow += t, ew[ed] -= t, ew[ed ^ 1] += t;
}
if(flow == li) return flow;
}
if(--gap[d[u]] == 0) d[S] = N;
++gap[++d[u]];
return flow;
}
int ISAP() {
int flow = 0;
for(bfs(); d[S] < N; flow += dfs(S, inf)) memcpy(cur, head, sizeof(head));
return flow;
}
int vis[N];
void dfs_find(int u) {
vis[u] = 1;
for(int ed = head[u]; ~ed; ed = nx[ed]) {
int v = to[ed];
if(vis[v] || !ew[ed]) continue;
dfs_find(v);
}
}
int main() {
cin >> m >> n;
memset(head, -1, sizeof(head));
for(int i = 1; i <= m; i++) {
int p;
cin >> p;
sump += p;
Adde(S, i, p);
get_tools(i);
}
for(int i = 1; i <= n; i++) {
int q;
cin >> q;
Adde(i + m, T, q);
}
sump -= ISAP();
dfs_find(S);
for(int i = 1; i <= m; i++) if(vis[i]) printf("%d ", i); puts("");
for(int i = 1; i <= n; i++) if(vis[i + m]) printf("%d ", i); puts("");
printf("%d\n", sump);
return 0;
}
P2764 最小路径覆盖问题
考虑把每个点 \(u\) 拆成入点 \(u_i\) 和出点 \(u_o\), 每条边 \((u,v)\) 变成 \((u_o, v_i)\)。
不妨换个角度:一开始每个点自成一条路径,每选择一条边可以合并两条路径。最终路径最少也即合并次数最多。限制是每个点的入边最多选一条,出边也是。也即 \(u_i, u_o\) 相邻的边最多选一条。这就是最大匹配了,可以匈牙利或最大流做。
输出方案就直接挑出选择的边,标记每个点在方案中的前驱和后继,最后对每个前驱的点作为路径起点往后走。
不妨趁机复习一下二分图,仅列举关键词:
最大匹配 = 最小点覆盖 = n - 最大独立集 = n - 最小路径覆盖
最小可交路径覆盖 = 传递闭包最小路径覆盖
Hall 定理 / 正则图匹配 / Vizing 定理
Dilworth 定理
P2765 魔术球问题
明显柱子越多能放的球越多。一个一个往里放,看什么时候需要的柱子数量超过 \(n\)。
由于放的有顺序,我们可以如下连边:
发现最少需要的柱子数就是最小路径覆盖。
可以二分答案,也可以逐步添加点和边,因为 dinic 可以分段来跑。
最后输出方案可以重新跑一次 dinic。
P3254 圆桌问题
\(D_i\) 表示单位 \(i\), \(H_i\) 表示餐桌 \(i\)。建边:
意义显然,跑最大流即可。输出方案就看 \(D_i \rightarrow H_j\) 有没有流满。
P2766 最长不下降子序列问题
第二问可以考虑在第一问 DP 的状态机上去跑 flow。
具体来说,就是记 \(f_i\) 表示以每个点结尾的最长不下降子序列的长度。对 \(f_u = 1\),连 \(S \rightarrow u\),对 \(f_u = s\),连 \(u \rightarrow T\),对 \(f_u + 1 = f_v \wedge x_u \le x_v\),连边 \(u \rightarrow v\)。(以上边权均为 \(1\))这样就一定选的是最长不下降子序列。限制每个点只能选一次那就每个点拆成入出点,入点向出点连边容量为 \(1\),上述边改改形式。
第三问就 \(x_1\) 和 \(x_n\) 的入点向出点连边容量以及它们和 \(S, T\) 连边(如果有)容量改为 $ + \infty$,删掉 \(1 \rightarrow n\)(如果有)。若 \(s = 2\) 且 \(x_1 \le x_n\) 答案特别加 \(1\)。
好像要特判 \(n = 1\)
P2763 试题库问题
\(S\) 向每题连边流量 \(1\),题目向其所属类别连边流量 \(1\),类别向 \(T\) 连边流量为它所需要的题数。跑最大流看它能不能满流,方案看中间层,比较简单。
P2774 方格取数问题
二分图带权最大独立集。先假设所有点都选,考虑一些丢掉。
所有 \(2 \mid i + j\) 的点放在左部,其余放在右部。左部点向右部的相邻点连边 \(W = + \infty\)。\(S\) 向左部点连边,右部点向 \(T\) 连边,边权为该点点权 \(a_{i, j}\)。求最小割即可。
P1251 餐巾计划问题
费用流的模板。首先是板子:建边时反向边的费用为正向边的相反数,flow 就是把 dinic 的 bfs 改成(关于费用的)SPFA,dfs 时注意不能一个点重复走。只要一开始没有费用负环,那么可以证明后面也不会出现,SPFA 就无需判负环。
然后是建图。费用流一定谨记两句话:满流对应限制,少费求解最优。不搞清楚什么当流量什么当费用就不可能搞明白怎么建边。
此题中正是要用满流来确保每日餐巾足够,求解费用最少。
那么我们可以每天拆成入点 \(u_i\) 与出点 \(u'_i\),建边:\(u_i \rightarrow T, W = r_i,C = 0\),表示每天开始前必须拿 \(r_i\) 块新餐巾去用;\(S \rightarrow u_i, W = +\infty, C = p\),表示每天开始前可以任意买餐巾。\(S \rightarrow u'_i, W = r_i, C = 0\),每天结束后得到旧餐巾。\(u'_i \rightarrow u'_{i+1}, W = +\infty, C = 0\),留到明天洗。\(u'_i \rightarrow u_{i+m}, W = +\infty, C = f\);\(u'_i \rightarrow u_{i+n}, W = +\infty, C = s\),表示快洗和慢洗。跑 dinic 即可。
P2761 软件补丁问题
跟网络流没啥关系。状压跑最短路即可。当然这个时间复杂度分析有点迷。
P4013 数字梯形问题
与餐巾计划问题相比之下很简单(这道才是板子吧)。
规则 \(1\):和前面一样,拆点,入点向出点连一条边,\(W = 1, C = -a_{i, j}\),这样就每个点只用一次了。两层间就上层出点向下层入点连一条边,\(W = 1, C = 0\),其余自己推一下。
规则 \(2\):入点向出点连边的 \(W\) 改为 \(+ \infty\) ,最后一层向 \(T\) 也一样,表示(除了第一层)每个点可用无数次。
规则 \(3\):层间的边 \(W\) 也改为 \(+ \infty\)。
P4015 运输问题
这题你看到绝对不会陌生。因为 \(m = n = 2\) 就是一道经典的初中数学题。
当然题目本身很简单。\(u_i\) 表示第 \(i\) 个仓库,\(v_i\) 表示第 \(i\) 个零售商店。
默认 \(W = +\infty, C = 0\)。
\(S \rightarrow u_i, W = a_i\);\(v_i \rightarrow T, W = b_i\)。
\(u_i \rightarrow v_j, C = c_{ij}\)。
跑费用流。
P4014 分配问题
默认 \(W = 1, C = 0\)。
\(u_i\) 表示第 \(i\) 个人,\(v_i\) 表示第 \(i\) 件工作。
\(S \rightarrow u_i; v_i \rightarrow T\)。
\(u_i \rightarrow v_j, C = -c_{i, j}\)。
跑费用流。
P4016 负载平衡问题
这题甚至可以贪心。不过也可以费用流就是了。
默认 \(W = +\infty, C = 0\)。
\(S \rightarrow u_i, W = a_i; u_i \rightarrow T, W = \overline{a}\)
环上相邻点之间建边 \(u \leftrightarrow v, C = 1\)
跑费用流。
P3358 最长k可重区间集问题
可以这么想:相当于若干件任务在 \(t\) 天内选一些完成,每件任务需要一个人 \([l, r]\) 时间段内出差去干,可获得收益 \(r - l\),只有 \(k\) 个人可供调遣。问收益最大值。
建图 \(S \rightarrow 1 \rightarrow 2 \rightarrow \cdots \rightarrow t \rightarrow T, W = k, C = 0\)。
对于 \([l, r]\) 开区间建边 \(l \rightarrow r, W = 1, C = -(r - l)\)。表示选出这个开区间(任务)获得这么多收益,但是区间内的点可承受的线段数减一(调一个人去出差)。跑费用流。
CTSC1999 家园 / P2754 星际转移问题
可以枚举答案 \(t\),然后建立点 \((i, t)\) 表示在时间 \(t\) 站在太空站 \(i\) 上。边就是太空车按照时间转移,以及任意时刻从源点到地球 / 从月球到汇点。看什么时候最大流 \(\ge k\) 就是答案。
P4011 孤岛营救问题
状压手上有的钥匙是哪些,记为 \(P\)。
记 \(f_{i, j, P}\) 表示从一开始到人在 \((i, j)\) 钥匙状态为 \(P\) 的最短时间。转移就是 \((i, j, P) \rightarrow (i', j', P)\) 其中边的长度为 \(1\),要求 \((i, j)\) 与 \((i', j')\) 相邻。还有 \((i, j, P) \rightarrow (i, j, P \cup p_{i,j})\) 其中边的长度为 \(0\),表示捡钥匙。
跑最短路即可,可以用 01 bfs。
和网络流有啥关系
P2770 航空路线问题
可以视为起点向终点飞两次。那么拆点,起点,终点容量为 \(2\),分别直接连 \(S,T\)。其余点内部边容量为 \(1\),费用为 \(-1\)。这样跑费用流就行。
P4009 汽车加油行驶问题
可以建立分层图来维护油量限制。每层往下一层连边,有油库的除外,强制回到第一层并支付费用。没有油库的也提供这个选择,但是费用多一些。
P4012 深海机器人问题
这题在边上贡献,就无需拆点了。边只有一次可以算费用,其余流量管够,费用为 0。
P3356 火星探险问题
这题也近乎板子。还是把车路线当流量,石头当作费用,拆点满足限制就行。
P3355 骑士共存问题
二分图最大独立集模板。
P3357 最长k可重线段集问题
投影到 \(x\) 轴上就变成 P3358 了。开区间,还要拆点,\(x_0 \neq x_1\) 就 \(x_0\) 的出点向 \(x_1\) 入点连边,\(x_0 = x_1\) 就入点向出点连边即可。

浙公网安备 33010602011771号