网络流 口胡
我是口胡大王
允许负流量有上下界的源-汇最大流(已实现)
- 连 上界 ,下界 的边
- 无源汇可行流 有源汇最大流,注意到此时已经没有负容量了;找到此时 边的流量
- 删 点、 的 边,再跑最大流
无负环的最小费用源-汇最大流(已实现)
- 直接 Simplex
有上下界、负权环和源汇的最小费用可行流、最大流和最小流(已实现)
- 先连 上界 ,下界 ,费用 的边
- 无源汇可行流 有源汇最大流,判断是否可行,找到此时 边的流量
- 把这张图复制三遍,跑三遍 Simplex
全非负权的最小权稀疏二分图匹配(已实现)
- 费用流,秒了!
最小权稠密二分图匹配(已实现)
- 费用流,秒了!
最大最小欧拉回路(已实现)
- 二分答案,问题变成:给无向边定向,问能不能构成欧拉回路
- 有向图为欧拉图的充要条件:每个点出度等于度数的一半
- 若边 有向,他给 提供 1 的出度,若无向,给 或 提供 1 的出度
- 边 建点 ,点 建点 , 容量为 , 容量为 ,若 边 有向,则 向 连容量为 的边,否则向 连容量为 的边
- 跑最大流,看是否满流、再看哪些 流过了即可定向,最后 DFS 输出方案即可。
= P3511
环形铁路
数学做法
- 总货物数一定,故每个库最后拥有的货物数一定,故每个库要转出或转入的货物数一定
- 因为每个库只能和两个库交易,故确定和一个库的交易后即可确定与另一个库的交易
- 推广发现,若确定其中一组相邻的交易,就能确定其余所有交易,答案就是个一元函数
- 设 交易了 ,每个库所需转出量为 ,则 交易 , 交易 , 交易 ……
- 设 为 前缀和,,搞出中位数代入计算即可
费用流做法
- 设货物数量序列为 ,,下标从 开始, 为容量、 为费用
- 建点 ,每个仓库 建点
- ,,,,
- ,,,
- ,,,
- 跑最小费用最大流,最小费用即为答案
= P4016
卖猪
- 建点 ,每个客人 建点
- :
- 若 是 的第一个客人,,
- 否则,设上一个客人为 ,,
- ,,
- 跑最大流,最大总流量即为答案
= POJ 1149
志愿者招募
- 设 为流量下界, 为流量上界, 为费用
- 第 天建点
- ,,,,
- ,,,,
- ,,,,
- 跑最小费用可行流,最小费用即为答案
= P3980
餐巾问题
- 设 为流量下界, 为流量上界, 为费用
- 建点 ,第 天建点
- :
- ,,,
- ,,,
- ,,,
- ,,,,
- ,,,,
- ,,,,
- 跑最小费用可行流,最小费用即为答案
= P1251
星际转移问题
- 设 为 船的停靠站周期序列( 内下标从 0 开始),, 为容量
- 无解条件:
- ,并查集中合并 连通块
- 若 不连通,则无解
- 从小到大枚举答案 ,每次在上一张图的残量网络上继续加边继续跑
- 建点 ,对于每个站点 、每个时间 ,建点
- 时:
- ,
- ,
- 否则,新加边:
- ,,
- ,,
- ,
- 若满流则 满足条件,退出
- 答案上界:
- 研究一条可行的流,他的流量至少为
- 首先他不会重复经过一个点,因为你总可以将其替换成一直在该点停留
- 其次在一个点停留的时间至多为 ,因为假如存在一个经过该点的船,他最多隔 的时间就会访问到该点
- 故答案上界为
- 因为答案 ,故点数、边数不会爆炸
- 该方法比直接二分快
= P2754
管道清洁
- 设 为流量下界, 为流量上界, 为费用
- 建边:
- 对于一号边 ,建边 ,,,
- 对于二号边 ,建边 ,,,
- 对于三号边 ,建边 ,,,
- 对于四号边 ,建边 ,,,
- 把 号点拆为 , 的所有出边由 连出、所有入边由 连入
- 跑源-汇最小费用可行流,可以判有/无解,若有解则最小费用为答案
宝石问题
- 设机器人起点集合为 、终点集合为 , 为容量, 为费用
- 建点 ,每个格子 拆为两个点
- :
- ,,
- ,,
- ,,,
- ,,,
- ,,,
- ,,,
- 跑源-汇最小费用最大流,最小费用的相反数即为答案
双向路径问题
暴力
- 设 为容量, 为费用
- 建点 ,原图每个点 拆为
- ,,,
- ,,
- ,,
- 对于原图边 ,建边 ,,
- 跑最大费用最大流,若满流则有解,答案为最大费用
- ,不能通过
优化 1
- 用单纯形网络流碾过去
- 理论复杂度为指数级
优化 2
- 考虑原始对偶的过程,发现第一次 SPFA 可以替换成 DAG 上的 DP
同余最大流
- 设 为流量, 为流量下界, 为流量上界
- 首先,
- 原题相当于,你要对每条边 定一个 ,令其流量为 ,需要满足:
- 流量平衡条件
- 流量上下界条件
- 有解情况下,最大化
- 首先,若 ,则无解
- 令
- 考虑建出新图,新图中每条边的流量对应原图中该边的
- 建点 ,对于原图中每个点 建点
- 对原图每条边 算出满足 的 的取值范围 ,若不存在 使条件满足,则无解;否则在新图中建边 ,,
- 对于每个点 ,若 ,建边 ,,;若 ,建边 ,,
- 先以 为源点、 为汇点做源-汇最大流,求出一组可行流;然后删去 点,在可行流的基础上做以 为源点、 为汇点的源-汇最大流
最小割模型
最小割问题
最大流 = 最小割
最大权闭合子图
闭合子图:对于有向图,我们选一些点,若 被选,则任意 连出的点 都被选。
最大权闭合子图:每个点有点权,要求闭合子图点权和最大
- 设原图 ,点 权值为 ,边权为
- 建点 ,每个点 建点
- ,建边 ,
- ,建边 ,
- ,建边 ,
- 答案 正点权和 最小割
正确性:
- 先把所有正点权给选了
- 集合表示选的点集, 集合表示不选的点集
- 第 3 类边表示,若 选了, 也必须选
- 第 1 类边表示,若正权点 不被选,代价增加
- 第 2 类边表示,若负权点 被选,代价增加
最小点割集
一张图,给出 ,每个点有正点权,求一个不包含 的权值和最小的点集,使得删掉点集中所有点后 无法到达 。
- 把点拆成入点和出点,入点向出点连边,边权为该点点权
- 原图边从出点连向入点,边权为
- 最小割即为答案
最小冲突投票
例题:BZOJ 1934
个人, 对好友关系,每个人可以给 A 或 B 投票,每个人都有自己的意愿(倾向于 A 还是 B)。
定义一次投票的冲突数为“好友之间投票冲突的总数”加上“和自己本来意愿发生冲突的人数”,问如何投票,冲突数最小。
- 令 表示投 A 的集合, 表示投 B 的集合, 为边权
- 建点
- :
- 若 想投 A,,
- 若 想投 B,,
- 对于一对好友关系 ,连边 ,
- 最小割即为答案
组合收益
每个物品 有收益 (可以为负),若 同时被选会额外获得收益 ,问最大收益。
- 先把所有正收益全选了
- 令 集合表示选的物品, 集合表示没选的物品
- 每个组合 建点
- 若 ,,
- 若 ,,
- 、,
- 若 ,,
- 若 ,,
- 答案等于正收益之和减去最小割
黑白染色
把所有点分为两个集合, 在两个集合分别获得的收益为 ,如果 所在集合不同会获得一个收益,否则会付出一个代价。
- 先把所有点黑白染色,分成两类点,需要满足同一类点之间没有限制条件
- 令 为在第一个集合, 为在第二个集合
- 对于第一类收益:
- 对于黑色点,正常向 连边
- 对于白色点,反转源汇,即把 看成 、把 看成 ,然后连边
- 对于第二类收益:
- 问题变成了若所在集合相同则获得收益、所在集合不同则付出代价,正常连边即可
- 答案为所有收益之和减去最小割
最小割树
无向图最小割性质:设 为任意两点,设 到 的最小割为 ,且把点分成了集合 和 ,则 , 到 的最小割
最小割树:点集中任取 ,在原图中跑出最小割 把点集分成 ,按 划分当前点集,向两边递归,再用一个权值为 的点连接两边的点集,像 Kruskal 重构树一样
最小割等于 路径上的点权最小值.
证明考虑最小割的最小性和唯一性。
例题
最大密度子图
定义无向图 的密度为 ,问子图的最大密度及方案。
- 二分答案 ,问题变成判定:有没有子图满足 ,即求出
- 整理条件:
- 选择子图时,如果选了某条边,那么他的两个端点也必须被选
- 选了一条边,产生贡献
- 选了一个点,产生贡献
- 得出方法:
- 把原图的点、边一律视为点
- 从边变成的点向其两个端点连一条有向边
- 将边的点权设为 ,点的点权设为
- 在新图中求出最大权闭合子图,答案就是我们要求的
- 对于输出方案,把选了的点和边输出即可
最小权点覆盖
对于二分图,选一些点去覆盖他们相邻的边,使得所有边被覆盖,问选出的点权和最小是多少。
- 建点
- 对于所有左部点 ,,权值为 的点权
- 对于所有右部点 ,,权值为 的点权
- 对于原图边 ,,权值为
- 求出最小割即为答案
这里割一条边就相当于选一个点
最大权独立集
等于总权值 最小权点覆盖,之前证过。
习题
植物大战僵尸
最大权闭合子图。注意环。
奶牛的电信
最小点割集。
最小边异或和
- 首先按位考虑,每个点要么是 1,要么是 0,要么还没定,目标是最小化异或和
- 令 为 0 集合, 为 1 集合
- 对于原图边 :
- 若两个点权已经给定,直接计入答案
- 若给定一个点权,假设为 :
- 若 ,连边 ,权值为
- 若 ,连边 ,权值为
- 若两个点权都待定,连边 ,权值为
- 最小割即为答案
逃出包围圈
最小点割集。
建图就是把上平面、下平面,以及每个圆看成一个点,把相交 / 相切关系看成一条边
猫狗大战
- 设 为获奖的猫 / 不获奖的狗, 为不获奖的猫 / 获奖的狗
- 建点 ,猫 建点 ,狗 建点
- 对于所有猫 ,设其支持人数为 ,,
- 对于所有狗 ,设其支持人数为 ,,
- 对于所有猫 、所有狗 ,设支持猫 、不支持狗 的猫粉数量,与不支持猫 、支持狗 的狗粉数量之和为 ,,
- 总人数减去最小割即为答案
综合习题
无限之环
黑白染色 + 大力讨论 + 费用流
Code
#include <bits/stdc++.h> using namespace std; #define rep(i, j, k) for (int i = (j); i <= (k); ++i) #define reo(i, j, k) for (int i = (j); i >= (k); --i) typedef long long ll; const int N = 2001, dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} }; const int INF = 0x3f3f3f3f; vector<vector<array<int, 5>>> p; vector<vector<int>> a, b; int n, m, s, t; struct Edge { int u, v, n, w, c; } e[50001]; int pnode, pedge = 1, h[10001], cur[10001]; int SumF; void add_edge(int u, int v, int w, int c) { e[++pedge] = {u, v, h[u], w, c}, h[u] = cur[u] = pedge; } void Add_edge(int u, int v, int w, int c, int d = 0) { if (d) swap(u, v); add_edge(u, v, w, c), add_edge(v, u, 0, -c); } void build_graph() { p.assign(n, vector<array<int, 5>>(m)); b.assign(n, vector<int>(m)); rep(i, 0, n - 1) rep(j, 0, m - 1) rep(k, 0, 4) p[i][j][k] = ++pnode; s = ++pnode, t = ++pnode; rep(i, 0, n - 1) rep(j, 0, m - 1) b[i][j] = (i + j) % 2; rep(i, 0, n - 1) { rep(j, 0, m - 1) { if (!b[i][j]) { rep(k, 0, 3) { int x = i + dir[k][0], y = j + dir[k][1]; if (0 <= x && x <= n - 1 && 0 <= y && y <= m - 1) { Add_edge(p[i][j][k + 1], p[x][y][(k ^ 2) + 1], 1, 0); Add_edge(p[x][y][(k ^ 2) + 1], p[i][j][k + 1], 1, 0); } } } } } rep(i, 0, n - 1) { rep(j, 0, m - 1) { int d = __builtin_popcount(a[i][j]); if (b[i][j] == 0) { Add_edge(s, p[i][j][0], d, 0); SumF += d; } else { Add_edge(p[i][j][0], t, d, 0); } } } rep(i, 0, n - 1) { rep(j, 0, m - 1) { if (a[i][j] == 0) { ; } if (a[i][j] == 1) { Add_edge(p[i][j][0], p[i][j][1], 1, 0, b[i][j]); Add_edge(p[i][j][1], p[i][j][2], 1, 1, b[i][j]); Add_edge(p[i][j][1], p[i][j][4], 1, 1, b[i][j]); Add_edge(p[i][j][1], p[i][j][3], 1, 2, b[i][j]); } if (a[i][j] == 2) { Add_edge(p[i][j][0], p[i][j][2], 1, 0, b[i][j]); Add_edge(p[i][j][2], p[i][j][1], 1, 1, b[i][j]); Add_edge(p[i][j][2], p[i][j][3], 1, 1, b[i][j]); Add_edge(p[i][j][2], p[i][j][4], 1, 2, b[i][j]); } if (a[i][j] == 3) { Add_edge(p[i][j][0], p[i][j][1], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][2], 1, 0, b[i][j]); Add_edge(p[i][j][1], p[i][j][3], 1, 1, b[i][j]); Add_edge(p[i][j][2], p[i][j][4], 1, 1, b[i][j]); } if (a[i][j] == 4) { Add_edge(p[i][j][0], p[i][j][3], 1, 0, b[i][j]); Add_edge(p[i][j][3], p[i][j][4], 1, 1, b[i][j]); Add_edge(p[i][j][3], p[i][j][2], 1, 1, b[i][j]); Add_edge(p[i][j][3], p[i][j][1], 1, 2, b[i][j]); } if (a[i][j] == 5) { Add_edge(p[i][j][0], p[i][j][1], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][3], 1, 0, b[i][j]); } if (a[i][j] == 6) { Add_edge(p[i][j][0], p[i][j][2], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][3], 1, 0, b[i][j]); Add_edge(p[i][j][2], p[i][j][4], 1, 1, b[i][j]); Add_edge(p[i][j][3], p[i][j][1], 1, 1, b[i][j]); } if (a[i][j] == 7) { Add_edge(p[i][j][0], p[i][j][1], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][2], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][3], 1, 0, b[i][j]); Add_edge(p[i][j][1], p[i][j][4], 1, 1, b[i][j]); Add_edge(p[i][j][3], p[i][j][4], 1, 1, b[i][j]); Add_edge(p[i][j][2], p[i][j][4], 1, 2, b[i][j]); } if (a[i][j] == 8) { Add_edge(p[i][j][0], p[i][j][4], 1, 0, b[i][j]); Add_edge(p[i][j][4], p[i][j][1], 1, 1, b[i][j]); Add_edge(p[i][j][4], p[i][j][3], 1, 1, b[i][j]); Add_edge(p[i][j][4], p[i][j][2], 1, 2, b[i][j]); } if (a[i][j] == 9) { Add_edge(p[i][j][0], p[i][j][1], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][4], 1, 0, b[i][j]); Add_edge(p[i][j][1], p[i][j][3], 1, 1, b[i][j]); Add_edge(p[i][j][4], p[i][j][2], 1, 1, b[i][j]); } if (a[i][j] == 10) { Add_edge(p[i][j][0], p[i][j][2], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][4], 1, 0, b[i][j]); } if (a[i][j] == 11) { Add_edge(p[i][j][0], p[i][j][1], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][2], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][4], 1, 0, b[i][j]); Add_edge(p[i][j][2], p[i][j][3], 1, 1, b[i][j]); Add_edge(p[i][j][4], p[i][j][3], 1, 1, b[i][j]); Add_edge(p[i][j][1], p[i][j][3], 1, 2, b[i][j]); } if (a[i][j] == 12) { Add_edge(p[i][j][0], p[i][j][3], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][4], 1, 0, b[i][j]); Add_edge(p[i][j][3], p[i][j][1], 1, 1, b[i][j]); Add_edge(p[i][j][4], p[i][j][2], 1, 1, b[i][j]); } if (a[i][j] == 13) { Add_edge(p[i][j][0], p[i][j][1], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][3], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][4], 1, 0, b[i][j]); Add_edge(p[i][j][1], p[i][j][2], 1, 1, b[i][j]); Add_edge(p[i][j][3], p[i][j][2], 1, 1, b[i][j]); Add_edge(p[i][j][4], p[i][j][2], 1, 2, b[i][j]); } if (a[i][j] == 14) { Add_edge(p[i][j][0], p[i][j][2], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][3], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][4], 1, 0, b[i][j]); Add_edge(p[i][j][2], p[i][j][1], 1, 1, b[i][j]); Add_edge(p[i][j][4], p[i][j][1], 1, 1, b[i][j]); Add_edge(p[i][j][3], p[i][j][1], 1, 2, b[i][j]); } if (a[i][j] == 15) { Add_edge(p[i][j][0], p[i][j][1], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][2], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][3], 1, 0, b[i][j]); Add_edge(p[i][j][0], p[i][j][4], 1, 0, b[i][j]); } } } } int nw, fa[10001], fe[10001], cir[10001], tag[10001]; int sum[10001]; void DFS(int u, int f, int c) { fa[u] = e[f].u, fe[u] = f, tag[u] = c; for (int i = h[u]; i; i = e[i].n) if (e[i].w && tag[e[i].v] != c) DFS(e[i].v, i, c); } int Sum(int u) { if (tag[u] == nw) return sum[u]; return tag[u] = nw, sum[u] = Sum(fa[u]) + e[fe[u]].c; } int Push(int o) { int cnt = 0, del = 0, P = 2; int C = 0, F = e[o].w; ++nw; int q = e[o].u, lca = e[o].v; while (q) tag[q] = nw, q = fa[q]; while (tag[lca] != nw) tag[lca] = nw, lca = fa[lca]; for (int u = e[o].u; u != lca; u = fa[u]) { cir[++cnt] = fe[u]; if (e[fe[u]].w < F) F = e[fe[u]].w, del = u, P = 0; } for (int u = e[o].v; u != lca; u = fa[u]) { cir[++cnt] = fe[u] ^ 1; if (e[fe[u] ^ 1].w < F) F = e[fe[u] ^ 1].w, del = u, P = 1; } cir[++cnt] = o; rep(i, 1, cnt) C += F * e[cir[i]].c, e[cir[i]].w -= F, e[cir[i] ^ 1].w += F; if (P == 2) return C; int u = e[o].u, v = e[o].v; if (P) swap(u, v); int lste = o ^ P, lstu = v, tmp; while (lstu != del) { swap(fe[u], lste ^= 1), --tag[u]; tmp = fa[u], fa[u] = lstu, lstu = u, u = tmp; } return C; } void Simplex() { Add_edge(t, s, INF, -INF); DFS(t, 0, ++nw), tag[t] = ++nw, fa[t] = 0; int F = 0, C = 0, ok = 1; while (ok) { ok = 0; rep(i, 2, pedge) if (e[i].w && e[i].c + Sum(e[i].u) - Sum(e[i].v) < 0) C += Push(i), ok = 1; } F = e[pedge].w; C += e[pedge].w * INF; if (F == SumF) { cout << C << '\n'; } else { cout << "-1\n"; } } int main() { ios::sync_with_stdio(false), cin.tie(nullptr); cin >> n >> m; a.assign(n, vector<int>(m)); rep(i, 0, n - 1) { rep(j, 0, m - 1) { cin >> a[i][j]; } } build_graph(); Simplex(); return 0; }
本文作者:Laijinyi
本文链接:https://www.cnblogs.com/laijinyi/p/18544610
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步