网络流学习笔记
前言
本人已做完网络流24题。
网络流难点在建模而不是代码(可以理解为图论版的dp),所以刚入门不必纠结于Dinic的写法(甚至硬背下来都没关系),重点在学建模上。
基础:
Dinic-最大流/最小割
typedef long long ll; const int N = 1e4 + 5; const int M = 1e6 + 5; const ll inf = 1e18; int n, m, s, t, tot, head[N], to[M], nxt[M]; ll val[M], d[N]; inline void link(int u, int v, ll w) { to[tot] = v; nxt[tot] = head[u]; val[tot] = w; head[u] = tot++; } inline void add(int u, int v, ll w) { link(u, v, w); link(v, u, 0); } queue<int> q; inline bool bfs() { q = queue<int>(); memset(d, 0, sizeof(d)); d[s] = 1; q.emplace(s); while (!q.empty()) { int tmp = q.front(); q.pop(); for (int i = head[tmp]; ~i; i = nxt[i]) if (!d[to[i]] && val[i]) { d[to[i]] = d[tmp] + 1; q.emplace(to[i]); if (to[i] == t) return true; } } return false; } inline ll dfs(ll x, ll flow) { if (x == t) return flow; ll rest = flow; for (int i = head[x]; ~i && rest; i = nxt[i]) if (d[to[i]] == d[x] + 1 && val[i]) { ll rs = dfs(to[i], min(rest, val[i])); if (!rs) d[to[i]] = 0; else { rest -= rs; val[i] -= rs; val[i ^ 1] += rs; } } if (rest == flow) d[x] = 0; return flow - rest; } inline ll Dinic() { ll res = 0; while (bfs()) res += dfs(s, inf); return res; }
Dinic-费用流
把bfs换成spfa即可。
typedef long long ll; const int N = 1e5 + 5; const int M = 2e6 + 5; const ll inf = 0x3f3f3f3f3f3f3f3f; int n, m, s, t, tot, head[N], to[M], nxt[M]; ll val[M], c[M], d[N], flw, cst; bool vis[N]; inline void link(int u, int v, ll w, ll cost) { to[tot] = v; nxt[tot] = head[u]; val[tot] = w; c[tot] = cost; head[u] = tot++; } inline void add(int u, int v, ll w, ll cost) { link(u, v, w, cost); link(v, u, 0, -cost); } queue<int> q; inline bool spfa() { memset(d, 0x3f, sizeof(d)); q.emplace(s); d[s] = 0; vis[s] = true; while (!q.empty()) { int tmp = q.front(); q.pop(); vis[tmp] = false; for (int i = head[tmp]; ~i; i = nxt[i]) if (val[i] && d[to[i]] > d[tmp] + c[i]) { d[to[i]] = d[tmp] + c[i]; if (!vis[to[i]]) { vis[to[i]] = true; q.emplace(to[i]); } } } return d[t] != inf; } inline ll dfs(ll x, ll flow) { if (x == t) return flow; vis[x] = true; ll rest = flow; for (int i = head[x]; ~i && rest; i = nxt[i]) if (!vis[to[i]] && val[i] && d[to[i]] == d[x] + c[i]) { ll rs = dfs(to[i], min(rest, val[i])); if (!rs) d[to[i]] = inf; else { rest -= rs; val[i] -= rs; val[i ^ 1] += rs; cst += rs * c[i]; } } if (rest == flow) d[x] = inf; vis[x] = false; return flow - rest; } inline void MCMF() { while (spfa()) flw += dfs(s, inf); cout << cst; }
正经人谁用HLPP。Dinic太强大了。
相同分组增加贡献
例题P1361 P1646。
先Dinic计算最小割,总和-最小割即为答案。
连边:
s→i[1,n],边权为i在第一组的贡献。
i[1,n]→t,边权为i在第二组的贡献。
如果第j个输入表示点集$S$都在第一组时会额外增加k的回报,连边:
s→j,边权为k;
j→$S$,边权为inf;
否则(如果在第二组):
j→t,边权为k;
$S$→j,边权为inf;
答案即为总边权(除去inf)减最小割。
P1361样例:
共用消耗,完成增加贡献
例题P2762 P4174。
分层连边:
对于每个任务i,设需要的工具集为$S$,做完可以获得k的回报:
s→i,边权为k;
i→$S$,对于$S$的每个点,边权为inf。
对于每个工具i:
i→t,边权为i的花费。
答案即为每个任务回报和减最小割。
P2762样例:
棋盘选点问题
例题P2774 P3355。
将点分为两组(直接用(x+y)&1分类即可)。
对于第一组点集$S$,连边$S$→t,边权为所求结果。
对于另外一组$S$,连边s→$S$,边权为所求结果;对于每个点u,连边u→$V$,$V$为和u相邻的点集,边权inf。
答案依旧为总和减最小割。
P2774样例:
分配问题 Basic
例题P4014 P4015。(P2763 P3254等其实也差不多)
连边:
s→i[1,n],流量1,费用0,表示每个人。
i[n+1,n*2]→t,流量1,费用0,表示每个任务。
i[1,n]->j[n+1,n*2],流量1,费用$c_{i,j}$。
其中P4014第二问还要求跑最大费用最大流,费用建负数即可。
边太多了,图就不放了。
分配问题 Plus
例题P2053 P2050。(P2050稍难,要求动态加点,相当于Ultimate版本)
其实就是模型4的升级版,将模型4里面每个人拆成m个人,即第i个人拆成第j时段的第i人即可。
重新建图/残量网络
例题P4013 P4014 P4015。
如果一题有很多问,而一问又可以转化为上一问的图+某些新的边(比如P4013第二问就是第一问再加上任意两点连边inf),那么就会涉及到这个。
如果是最大流(有这种题,但是我忘了),跑完Dinic后把答案存起来,在残量网络上建新的边,再跑一遍,两次答案之和即为第二次的流量。
但如果是费用流,就要重新建图了。P4013 P4014都是重新建图的例子。跑完后把链式前向星的head数组、链式前向星的tot、MaxFlow值、MinCost/MaxCost值全部初始化,再建图即可。
上下界网络流
上下界最大流
懒得画图了。放代码吧。
int s1 = n + m + 1, t1 = n + m + 2, s2 = n + m + 3, t2 = n + m + 4; s = s2, t = t2; for (int i = 1; i <= m; i++) { ll x; cin >> x; in[t1] += x; out[n + i] += x; add(n + i, t1, inf - x); } for (int i = 1; i <= n; i++) { ll x, y, T, l, r; cin >> x >> y; add(s1, i, y); for (int j = 1; j <= x; j++) { cin >> T >> l >> r; T++; in[n + T] += l; out[i] += l; add(i, n + T, r - l); } } ll maxFlow = 0; for (int i = 1; i <= n + m + 2; i++) if (in[i] > out[i]) add(s, i, in[i] - out[i]), maxFlow += in[i] - out[i]; else add(i, t, out[i] - in[i]); add(t1, s1, inf); if (Dinic() != maxFlow) { cout << "-1\n\n"; continue; } int ans = val[tot - 1]; val[tot - 1] = val[tot - 2] = 0; s = s1, t = t1; cout << ans + Dinic() << "\n\n";
题目
- 网络流24题
- 志愿者招募
- 石头剪刀布
- Nanami's Power Plant
写在最后
网络流24题里确实还有一些经典建模本文没有涉及到。因为本人很懒。