拓扑序:一张有向无环图中的一个节点序列 A,满足对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前。
维护一个入度为 0 的点集 S。每次扩展时,从 S 中取出任意一点 u 加入拓扑序,然后枚举 u 的所有出边,若某个出点 v 在删除了 (u,v) 这条边之后入度变为 0,则将 v 加入点集 S。
由拓扑排序的算法得到的拓扑序按照层次分层,但同一层次的节点未必需要在拓扑序中相邻。
若要求字典序最小的拓扑序,将队列换成优先队列即可。
int tot, head[N], ver[M], Next[M], deg[N];
voidadd_edge(int u, int v) {
ver[++ tot] = v; Next[tot] = head[u]; head[u] = tot; deg[v] ++;
}
int seqlen, seq[N];
voidtopsort() {
std::queue<int> q;
for (int i = 1; i <= n; i ++)
if (deg[i] == 0) q.push(i);
while (q.size()) {
int u = q.front(); q.pop();
seq[++ seqlen] = u;
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i];
if (-- deg[v] == 0) q.push(v);
}
}
}
欧拉路、欧拉回路
欧拉路:恰好不重不漏的经过每条边一次(可以重复经过图中的节点)的通路。
欧拉回路:恰好不重不漏的经过每条边一次(可以重复经过图中的节点)的回路。
欧拉图:存在欧拉回路的无向图。
欧拉图的判定:
一张无向图为欧拉图,当且仅当无向图连通,并且每个点的度数都是偶数。
一张有向图为欧拉图,当且仅当非零度点强联通,每个点的入度与出度相等。
欧拉路的存在性判定:一张图中存在欧拉路,当且仅当无向图连通,并且途中恰好有两个节点的度数为奇数,其他节点的度数都是偶数。这两个度数为奇数的节点就是欧拉路的起点 S 和终点 T。
枚举点 x,第一轮枚举 x 在新图中的出边,将所到达的点 z 打上标记;第二轮枚举 x 在新图中的出边,对于所到达的点 y,再枚举 y 在新图中的出边到达的点 z,如果 z 上有标记,则 x,y,z 构成三元环。
std::vector<int> e[N];
int id[N], rk[N];
std::vector<int> out[N];
bool vis[N];
intmain() {
for (int i = 1, u, v; i <= m; i ++)
e[u].push_back(v), e[v].push_back(u);
for (int i = 1; i <= n; i ++) id[i] = i;
std::sort(id + 1, id + 1 + n);
for (int i = 1; i <= n; i ++) rk[id[i]] = i;
for (int u = 1; u <= n; u ++)
for (int v : e[u]) if (rk[u] < rk[v]) out[u].push_back(v);
int ans = 0;
for (int x = 1; x <= n; x ++) {
for (int z : out[x]) vis[z] = 1;
for (int y : out[x])
for (int z : out[y])
if (vis[z]) ans ++;
for (int z : out[x]) vis[z] = 0;
}
}
无向图三元环计数,时间复杂度:O(m√m)。
无向图三元环计数,时间复杂度分析:
记 degu 为原图中点 u 的度,outu 为新图中点 u 的出度。可证 outu 为 O(√m) 级别。
若 degu≤√m,则 outu≤√m。
若 degu>√m,则 u 在新图中指向的点的度数大于 √m,故 outu≤√m。
上述流程的复杂度,相当于对定向后每条边 (u,v) 的 v 的 outv 求和。每条边的贡献为 O(√m) 级别,故上述流程的复杂度为 O(m√m)。
枚举点 x,再枚举 x 在原图中的出边,对于所到达的点 y,再枚举 y 在新图中的出边到达的点 z(为避免第三种边向情况重复计数,此处枚举到的 z 应满足 rkx<rkz),则先前枚举的所有形如 (x,y)、y→z 的路径都与当前枚举的路径构成四元环。
std::vector<int> e[N];
int id[N], rk[N];
std::vector<int> out[N];
int cnt[N];
intmain() {
for (int i = 1, u, v; i <= m; i ++)
e[u].push_back(v), e[v].push_back(u);
for (int i = 1; i <= n; i ++) id[i] = i;
std::sort(id + 1, id + 1 + n);
for (int i = 1; i <= n; i ++) rk[id[i]] = i;
for (int u = 1; u <= n; u ++)
for (int v : e[u]) if (rk[u] < rk[v]) out[u].push_back(v);
int ans = 0;
for (int x = 1; x <= n; x ++) {
for (int y : e[x])
for (int z : out[y])
if (rk[x] < rk[z]) ans += cnt[z] ++;
for (int y : e[x])
for (int z : out[y])
if (rk[x] < rk[z]) cnt[z] = 0;
}
}
int tot, head[N], ver[N * 2], Next[N * 2], deg[N];
voidadd_edge(int u, int v) {
ver[++ tot] = v; Next[tot] = head[u]; head[u] = tot; deg[v] ++;
}
int Fa[N];
voiddfs(int u) {
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i];
if (Fa[v]) continue;
Fa[v] = u;
dfs(v);
}
}
int prufer[N];
intmain() {
Fa[n] = -1, dfs(n);
int leaf = 0, p = 0;
for (int i = 1; i <= n; i ++)
if (deg[i] == 1) { leaf = p = i; break; }
for (int i = 1; i <= n - 2; i ++) {
int x = Fa[leaf];
prufer[i] = x;
if (-- deg[x] == 1 && x < p) {
leaf = x;
} else {
p ++;
while (deg[p] != 1) p ++;
leaf = p;
}
}
for (int i = 1; i <= n - 2; i ++)
printf("%d ", prufer[i]);
puts("");
}
int n;
int prufer[N];
int deg[N];
std::pair<int, int> e[N];
intmain() {
for (int i = 1; i <= n; i ++) deg[i] = 1;
for (int i = 1; i <= n - 2; i ++) deg[prufer[i]] ++;
int leaf = 0, p = 0;
for (int i = 1; i <= n; i ++)
if (deg[i] == 1) { leaf = p = i; break; }
for (int i = 1; i <= n - 2; i ++) {
int x = prufer[i];
e[i] = std::make_pair(leaf, x);
if (-- deg[x] == 1 && x < p) {
leaf = x;
} else {
p ++;
while (deg[p] != 1) p ++;
leaf = p;
}
}
e[n - 1] = std::make_pair(leaf, n);
}
structedg {int u, v, w;
bool operator < (const edg &rhs) const {
return w < rhs.w;
}
} e[M];
int fa[N];
intget(int x) {
return fa[x] == x ? x : fa[x] = get(fa[x]);
}
voidkruskal() {
std::sort(e + 1, e + 1 + m);
for (int i = 1; i <= n; i ++) fa[i] = i;
for (int i = 1; i <= m; i ++) {
int u = e[i].u, v = e[i].v, w = e[i].w;
int p = get(u), q = get(v);
if (p == q) continue;
// Calculate the information of the (u, v, w)
fa[p] = q;
}
}
Kruskal 重构树
Kruskal 重构树:按 Kruskal 的流程,每加入一条边 (u,v,w),就新建一个点 x,同时将 x 的点权设为 w,将 u,v 所在的树的根节点分别设为点 x 的左儿子与右儿子。在进行 n−1 轮合并后,得到了一棵恰有 n 个叶子的二叉树,同时每个非叶子节点恰好有两个儿子,这棵树即为 kruskal 重构树。
structedg {int u, v, w;
bool operator < (const edg &rhs) const {
return w < rhs.w;
}
} e[M];
namespace KRT {
constint SIZE = N * 2;
int nClock;
structnode {int lc, rc;
int val;
} t[SIZE];
int fa[SIZE];
intget(int x) {
return fa[x] == x ? x : fa[x] = get(fa[x]);
}
voidbuild() {
std::sort(e + 1, e + 1 + m);
nClock = n;
for (int i = 1; i <= n; i ++) fa[i] = i;
for (int i = 1; i <= m; i ++) {
int u = e[i].u, v = e[i].v, w = e[i].w;
int p = get(u), q = get(v);
if (p == q) continue;
int x = ++ nClock;
t[x].val = w, t[x].lc = p, t[x].rc = q;
fa[p] = fa[q] = fa[x] = x;
}
}
}
kruskal 最小重构树中,对于叶子节点 x 到根的路径上权值 ≤val 的最浅节点,其子树内的所有节点,即为从 x 开始只经过边权 ≤val 的边所能到达的节点(满足两点之间所有简单路径上最大边权的最小值 ≤val)。
Prim
O(n2)。
constint inf = 0x3f3f3f3f;
int a[N][N];
int d[N];
bool exist[N];
voidprim() {
for (int i = 1; i <= n; i ++) d[i] = inf;
d[1] = 0;
for (int i = 1; i <= n; i ++) exist[i] = 0;
for (int i = 1; i <= n; i ++) {
int u = 0;
for (int x = 1; x <= n; x ++)
if (!exist[x] && (u == 0 || d[x] < d[u])) u = x;
exist[u] = 1;
for (int v = 1; v <= n; v ++)
if (!exist[v]) d[v] = std::min(d[v], a[u][v]);
}
}
Brouvka
初始时,每一个点都是一个连通块。每一轮,遍历所有点和边,连接一个连通块中和其他连通块相连的最小边。
O((n+m)logn)。
constint inf = 0x3f3f3f3f;
structedg {int u, v, w;
} e[M];
int fa[N];
intget(int x) {
return fa[x] == x ? x : fa[x] = get(fa[x]);
}
int great_val[N], great_id[N];
voidboruvka() {
for (int i = 1; i <= n; i ++) fa[i] = i;
while (1) {
for (int i = 1; i <= n; i ++) great_val[i] = inf, great_id[i] = 0;
bool exist = 0;
for (int i = 1; i <= m; i ++) {
int p = get(e[i].u), q = get(e[i].v);
if (p == q) continue;
exist = 1;
if (e[i].w < great_val[p]) great_val[p] = e[i].w, great_id[p] = i;
if (e[i].w < great_val[q]) great_val[q] = e[i].w, great_id[q] = i;
}
if (!exist) break;
for (int i = 1; i <= n; i ++) {
if (great_id[i] == 0) continue;
int id = great_id[i], p = get(e[id].u), q = get(e[id].v);
if (p == q) continue;
// Calculate the data of this edge.
fa[p] = q;
}
}
}
constint inf = 0x3f3f3f3f;
int cur_block;
int fa[N];
intget(int x) {
return fa[x] == x ? x : fa[x] = get(fa[x]);
}
int great_val[N], great_id[N];
voidboruvka() {
cur_block = n;
for (int i = 1; i <= n; i ++) fa[i] = i;
while (cur_block ^ 1) {
for (int i = 1; i <= n; i ++) great_val[i] = inf, great_id[i] = 0;
for (int i = 1; i <= n; i ++) {
int p = get(i);
// update the data of this connected block p with i.
}
for (int i = 1; i <= n; i ++) {
if (great_id[i] == 0) continue;
int p = get(i), q = get(great_id[i]);
if (p == q) continue;
// Calculate the data of this edge.
fa[p] = q, cur_block --;
}
}
}
constint inf = 0x3f3f3f3f;
int tot, head[N], ver[M], edge[M], Next[M];
voidadd_edge(int u, int v, int w) {
ver[++ tot] = v; edge[tot] = w; Next[tot] = head[u]; head[u] = tot;
}
int dist[N];
bool vis[N];
voiddijkstra() {
for (int i = 1; i <= n; i ++) dist[i] = inf;
for (int i = 1; i <= n; i ++) vis[i] = 0;
std::priority_queue< pair<int, int> > q;
q.push(make_pair(0, 1)), dist[1] = 0;
while (q.size()) {
int u = q.top().second; q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i];
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
q.push(make_pair(-dist[v], v));
}
}
}
}
Bellman-ford & SPFA
SPFA:O(nm)。
constint inf = 0x3f3f3f3f;
int tot, head[N], ver[M], edge[M], Next[M];
voidadd_edge(int u, int v, int w) {
ver[++ tot] = v; edge[tot] = w; Next[tot] = head[u]; head[u] = tot;
}
int dist[N];
bool vis[N];
voidSPFA() {
for (int i = 1; i <= n; i ++) dist[i] = inf;
for (int i = 1; i <= n; i ++) vis[i] = 0;
std::queue<int> q;
q.push(1), dist[1] = 0;
while (q.size()) {
int u = q.front(); q.pop(); vis[u] = 0;
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i];
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
}
Bellman-Ford 判负环:若经过 n 轮迭代后,图中仍有能更新的边,则图中有负环。
SPFA 判负环:设 cntx 表示 1 到 x 的最短路包含的边数。在迭代的过程中,若发现 cnty≥n,则图中有负环。
Floyd
Floyd:O(n3)。
Floyd 本质上是 dp。定义 f(k,i,j) 表示允许经过 1 到 k 号点作为中间点(除了 i,j 分别为起点终点,经过的其他点只能是 1∼k 的点)的情况下,点 i 到点 j 的最短路。通过滚动数组优化掉了阶段维,才得到最常见的 Floyd 写法。如果以任意的顺序枚举 k,得到的 f(i,j) 即为允许经过枚举过的点作为中间点的情况下,点 i 到点 j 的最短路。
差分约束
差分约束:一种特殊的 n 元一次不等式组,包含 n 个变量 x1,x2,⋯,xn 以及 m 组限制关系 xi−xj≤ck。
xi−xj≤ck 可以变形为 xi≤xj+ck,形似三角形不等式,因此从点 j 向点 i 连一条长度为 ck 的有向边。
可以在 DFS 的过程中,维护一个栈,储存还未确定所属 e-DCC(可能有多个)的节点。找到 e-DCC 时,e-DCC 中的所有点都集中在栈顶端,只需不断弹出栈顶直到弹出 u 为止。被弹出的所有点,构成一个 e-DCC。
在找出了所有的 e-DCC 后,枚举原图中的所有边 (u,v),若 u 与 v 不属于同一个 e-DCC,则在 u,v 所属的 e-DCC 之间连一条边。
template <constint N, constint M>
structadj {int tot = 1, head[N], ver[M * 2], Next[M * 2];
voidadd_edge(int u, int v) {
ver[++ tot] = v; Next[tot] = head[u]; head[u] = tot;
}
};
adj<N, M * 2> G;
adj<N, N * 2> V;
int dfsClock, dfn[N], low[N];
int top, stk[N];
int e_dcc, col[N];
voidtarjan(int u, int in_edge) {
dfn[u] = low[u] = ++ dfsClock;
stk[++ top] = u;
for (int i = G.head[u]; i; i = G.Next[i]) {
int v = G.ver[i];
if (!dfn[v]) {
tarjan(v, i);
low[u] = std::min(low[u], low[v]);
} elseif (i != (in_edge ^ 1)) {
low[u] = std::min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
e_dcc ++;
int x;
do {
x = stk[top --];
col[x] = e_dcc;
} while (x != u);
}
}
intmain() {
for (int i = 1, u, v; i <= m; i ++)
G.add_edge(u, v), G.add_edge(v, u);
for (int i = 1; i <= n; i ++)
if (!dfn[i]) tarjan(i, 0);
for (int i = 2; i < G.tot; i += 2) {
int u = G.ver[i ^ 1], v = G.ver[i];
if (col[u] == col[v]) continue;
V.add_edge(col[u], col[v]), V.add_edge(col[v], col[u]);
}
}
有向图 tarjan
流图
时间戳 dfnx:在深度优先遍历的过程中,节点 x 第一次被访问时的时间顺序。
流图:给定有向图 G=(V,E),若存在 r∈V,满足从 r 出发能够到达 V 中的所有点,则称 G 是一个流图,其中 r 称为流图的源点。
流图中的有向边 (x,y) 分类:对于流图中的有向边 (x,y),必是以下四种之一
树枝边。指搜索树中的边,即 x 是 y 的父节点。
前向边。指搜索树中 x 是 y 的祖先节点。
后向边。指搜索树中 y 是 x 的祖先节点。
横叉边。指除了以上三种情况之外的边,一定满足 dfnx>dfny。
强连通分量
强连通:若既存在 x 到 y 的路径,又存在 y 到 x 的路径,则称 x 与 y 是强连通的。
强连通图:任意两点都强连通的有向图。
强连通分量(SCC):有向图的极大强连通子图。
时间戳 dfnx:在深度优先遍历的过程中,节点 x 第一次被访问时的时间顺序。
追溯值 lowx:以下节点的时间戳的最小值
栈中的节点。有向图 tarjan 在深度优先遍历的同时维护了一个栈,当访问到节点 x,保存从 x 出发的后向边、横叉边形成环的节点:
搜索树上 x 的祖先节点,记作 anc(x)。
已经访问过,并且存在一条路径到达 anc(x) 的节点。
通过一条从 subtree(x) 中出发的有向边,以该点为终点。
时间戳 dfnx 与追溯值 lowx 的计算方式:对整张图进行深度优先遍历,一开始 lowx=dfnx,将 x 入栈,考虑从 x 出发的每条边 (x,y)
若 y 没有访问过,则说明 x 为 y 的父亲,则
lowx=min(lowx,lowy)
若 y 被访问过,且 y 在栈中,则
lowx=min(lowx,dfny)
在 x 回溯之前,若 dfnx=lowx,则不断弹出栈顶直到弹出 x 为止。
SCC 判定法则:在 x 回溯之前,若 dfnx=lowx,则栈中从 x 到栈顶的所有节点构成一个 SCC。
SCC 的缩点:在找出了所有的 SCC 后,枚举原图中的所有边 (u,v),若 u 与 v 不属于同一个 SCC,则在 u,v 所属的 SCC 之间连一条边。
template <constint N, constint M>
structadj {int tot, head[N], ver[M], Next[M];
voidadd_edge(int u, int v) {
ver[++ tot] = v; Next[tot] = head[u]; head[u] = tot;
}
};
adj<N, M * 2> G, V;
int dfsClock, dfn[N], low[N];
int top, stk[N]; bool inc[N];
int scc, col[N];
std::vector<int> h[N];
voidtarjan(int u) {
dfn[u] = low[u] = ++ dfsClock;
stk[++ top] = u, inc[u] = 1;
for (int i = G.head[u]; i; i = G.Next[i]) {
int v = G.ver[i];
if (!dfn[v]) {
tarjan(v);
low[u] = std::min(low[u], low[v]);
} elseif (inc[v]) {
low[u] = std::min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
scc ++;
int x;
do {
x = stk[top --], inc[x] = 0;
col[x] = scc, h[scc].push_back(x);
} while (x != u);
}
}
intmain() {
for (int i = 1, u, v; i <= m; i ++)
G.add_edge(u, v);
for (int i = 1; i <= n; i ++)
if (!dfn[i]) tarjan(i);
for (int u = 1; u <= n; u ++)
for (int i = G.head[u]; i; i = G.Next[i]) {
int v = G.ver[i];
if (col[u] == col[v]) continue;
V.add_edge(col[u], col[v]);
}
}
tarjan 编号:tarjan 编号,为原图缩点后 DAG 的拓扑序反序。
2-SAT
2-SAT:有 n 个变量,每个变量只有两种可能的取值。给定 m 个条件,每个条件都形如「若 ai=p 则 aj=q」。求是否存在对 n 个变量的合法赋值,使得 m 个条件均得到满足。
int tot, head[N], ver[M * 2], Next[M * 2];
voidadd_edge(int u, int v) {
ver[++ tot] = v; Next[tot] = head[u]; head[u] = tot;
}
bool vis[N];
int match[N];
booldfs(int u) {
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i];
if (vis[v]) continue;
vis[v] = 1;
if (!match[v] || dfs(match[v])) {
match[v] = u;
return1;
}
}
return0;
}
intmain() {
int ans = 0;
for (int i = 1; i <= nl; i ++) {
memset(vis, 0, sizeof(vis));
if (dfs(i)) ans ++;
}
}
constint inf = 0x3f3f3f3f;
namespace nw {
constint Nx = ..., Mx = ...;
int S, T;
int tot = 1, head[Nx], ver[Mx * 2], edge[Mx * 2], Next[Mx * 2];
voidadd_edge(int u, int v, int w) {
ver[++ tot] = v; edge[tot] = w; Next[tot] = head[u]; head[u] = tot;
}
voidadd_network(int u, int v, int w) {
add_edge(u, v, w), add_edge(v, u, 0);
}
int lev[Nx];
int cur[Nx];
boolbfs() {
for (int i = S; i <= T; i ++) cur[i] = head[i];
for (int i = S; i <= T; i ++) lev[i] = 0;
std::queue<int> q; while (q.size()) q.pop();
q.push(S), lev[S] = 1;
while (q.size()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i];
if (w > 0 && !lev[v]) {
lev[v] = lev[u] + 1;
q.push(v);
if (v == T) return1;
}
}
}
return0;
}
intdfs(int u, int flow) {
if (u == T) return flow;
int res = 0;
for (int &i = cur[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i];
if (w > 0 && lev[u] < lev[v]) {
int delta = dfs(v, std::min(w, flow - res));
if (delta) {
edge[i] -= delta, edge[i ^ 1] += delta;
res += delta;
if (res == flow) break;
}
}
}
if (res < flow) lev[u] = 0;
return res;
}
intdinic() {
int max_flow = 0;
while (bfs()) max_flow += dfs(S, inf);
return max_flow;
}
}
常用模型
限制点的流量:可以考虑将点拆成「入点」与「出点」,从入点向出点连一条有容量的边。
最小割
割:在图 G=(V,E) 上,对于某个点集 P⊆V,割 (P,V∖P) 定义为
(P×(V∖P))⋂E
割的容量:割包含的边的容量之和,即
c(P,V∖P)=∑u∈P∑v∈V∖Pc(u,v)
s→t 割:满足 s∈P 且 t∈V∖P 的割。
最大流与最小割:在一张网络中,最大流等于最小割。
最大流与最小割证明:对于同一个网络的任意一个流 f 和任意一个 s→t 割 (S,T),均有
|f|=∑u∈S∑v∈Tf(u,v)≤∑u∈S∑v∈Tc(u,v)=c(S,T)
即任意流≤任意割。考虑 EK 算法求得的流 f,记流 f 对应的残量网络中从 s 出发可达的所有点组成的点集为 S,不可达的所有点组成的点集为 T,由于找不到流 f 的增广路,故 (S,T) 是一个 s→t 割,且由残量网络的定义可得 |f|=c(S,T)。故最大流等于最小割。
最大权闭合子图模型:有 n 个事件,事件发生会带来收益 wi,wi 可正可负,一个事件发生的前提是指定的若干事件必须发生。求最优的确定所有事件是否发生的方案,使得收益最大。
模型构造:
建源点 s 与汇点 t,对每个事件都建一个点,标号 1∼n。
若 wi>0,则令 S 向 i 连一条容量为 wi 的边;若 wi<0,则令 i 向 T 连一条容量为 −wi 的边。
对于原图中的边,容量设为 +∞。
模型分析:最大权闭合子图 = 正点权和 − 最小割。
在一个割中割去的边,负权点表示选择,正权点表示不选。
可以证明,在一个割中,已割去的负权点与未割去的正权点组成的子图 W 与原图的闭合子图对应。如果 W 不是闭合子图,出边可能指向已割去的正权点或未割去的负权点。若为前者,则 W 内的流越过了这个割;若为后者,则 W 内的流可以流向汇点。
费用流
EK & Dinic:每次需要扩展一条费用最小的增广路。
constint inf = 0x3f3f3f3f;
namespace nw {
constint Nx = ..., Mx = ...;
int S, T;
int tot = 1, head[Nx], ver[Mx * 2], Next[Mx * 2]; int edge[Mx * 2], cost[Mx * 2];
voidadd_edge(int u, int v, int w, int c) {
ver[++ tot] = v; edge[tot] = w; cost[tot] = c; Next[tot] = head[u]; head[u] = tot;
}
voidadd_network(int u, int v, int w, int c) {
add_edge(u, v, w, +c), add_edge(v, u, 0, -c);
}
int max_flow, min_cost;
int dist[Nx];
bool exist[Nx];
int cur[Nx];
bool vis[Nx];
boolspfa() {
for (int i = S; i <= T; i ++) cur[i] = head[i], vis[i] = 0;
for (int i = S; i <= T; i ++) dist[i] = inf;
std::queue<int> q;
q.push(S), dist[S] = 0;
while (q.size()) {
int u = q.front(); q.pop(); exist[u] = 0;
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i], c = cost[i];
if (w > 0 && dist[u] + c < dist[v]) {
dist[v] = dist[u] + c;
if (!exist[v]) {
exist[v] = 1;
q.push(v);
}
}
}
}
return dist[T] < inf;
}
intdfs(int u, int flow) {
if (u == T) {
min_cost += flow * dist[T];
return flow;
}
vis[u] = 1;
int res = 0;
for (int &i = cur[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i], c = cost[i];
if (w > 0 && !vis[v] && dist[u] + c == dist[v]) {
int delta = dfs(v, std::min(w, flow - res));
if (delta) {
edge[i] -= delta, edge[i ^ 1] += delta;
res += delta;
if (res == flow) break;
}
}
}
if (res == flow) vis[u] = 0;
return res;
}
voiddinic() {
max_flow = 0, min_cost = 0;
while (spfa()) max_flow += dfs(S, inf);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通