模板:
EK 求最大流
here
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1005, M = 20005,INF=1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], pre[N];
bool st[N];
void add(int a,int b,int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}
bool bfs(){
int hh = 0, tt = 0;
memset(st, 0, sizeof(st));
q[0] = S, st[S] = 1,d[S] = INF;
while(hh<=tt){
int t = q[hh++];
for (int i = h[t]; ~i;i=ne[i]){
int ver = e[i];
if(!st[ver]&&f[i]){
st[ver] = 1;
d[ver] = min(d[t], f[i]);
pre[ver] = i;
if(ver==T) return 1;
q[++tt] = ver;
}
}
}
return 0;
}
int EK(){
int r = 0;
while(bfs()){
r += d[T];
for (int i = T; i != S;i=e[pre[i]^1])
f[pre[i]] -= d[T], f[pre[i]^1] += d[T];
}
return r;
}
int main(){
memset(h, -1, sizeof(h));
cin >> n >> m >> S >> T;
while(m--){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
cout << EK() << endl;
return 0;
}
Dinic 求最大流
here
#include <bits/stdc++.h>
using namespace std;
const int N = 10005, M = 200005, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
void add(int a,int b,int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof(d));
q[0] = S, d[S] = 0, cur[S] = h[S];
while(hh<=tt){
int t = q[hh++];
for (int i = h[t]; ~i;i=ne[i]){
int ver = e[i];
if(d[ver]==-1&&f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if(ver==T)
return 1;
q[++tt] = ver;
}
}
}
return 0;
}
int find(int u,int limit){
if(u==T)
return limit;
int flow = 0;
for (int i = cur[u]; ~i&&flow<limit;i=ne[i]){
cur[u] = i;
int ver = e[i];
if(d[ver]==d[u]+1&&f[i]){
int t = find(ver, min(f[i], limit - flow));
if(!t) d[ver]=-1;
f[i] -= t, f[i ^ 1] += t;
flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while(bfs()){
while(flow=find(S,INF))
r += flow;
}
return r;
}
int main(){
memset(h, -1, sizeof(h));
cin >> n >> m >> S >> T;
while(m--){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, 0);
}
cout << dinic() << endl;
return 0;
}
匈牙利算法
here
#include <bits/stdc++.h>
using namespace std;
const int N = 10005, M = 200005;
int h[N], ne[M], cnt, e[M];
int match[N],vis[N];
int ans;
void add(int a,int b){
e[++cnt] = b,ne[cnt] = h[a],h[a] = cnt;
}
bool dfs(int x){
for (int i = h[x]; i;i=ne[i]){
int to = e[i];
if(vis[to])
continue;
vis[to] = 1;
if(match[to]==0||dfs(match[to])){
match[to] = x;
return true;
}
}
return false;
}
int main(){
int n, m, e;
cin >> n >> m >> e;
for (int i = 1; i <= e;i++){
int u, v;
cin >> u >> v;
add(u, v + n);
add(v + n, u);
}
for (int i = 1; i <= n;i++){
memset(vis, 0, sizeof(vis));
if(dfs(i))
ans++;
}
cout << ans << endl;
return 0;
}
题型思路总结:
-
P2756 飞行员配对方案问题
-
思路:
- 解法一:
观察到给定的图是一个二分图,求的就是二分图的最大匹配即可,时间复杂度 \(\mathcal{O}(nm)\) - 解法二:
网络流算法。从源点 \(S\) 向 点集 \(N\) 的每个点连接一个容量为 \(1\) 的边,代表每名飞行员只能匹配一次。 从点集 \(M\) 向汇点 \(T\) 连接一个容量为 \(1\) 的边,因为能量守恒,所以代表 \(M\) 点集里的同一个点不能多次使用,因为一旦多次使用会违背能量守恒定律。建图后跑 dinic 求最大流即可。时间复杂度 \(\mathcal{O}(\sqrt nm)\)
- 解法一:
-
代码:
here
#include <bits/stdc++.h> using namespace std; const int N = 10005, M = 200005, INF = 1e8; int n, m, S, T; int h[N], e[M], f[M], ne[M], idx; int q[N], d[N], cur[N]; void add(int a,int b,int c){ e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++; } bool bfs(){ int hh = 0, tt = 0; memset(d, -1, sizeof(d)); q[0] = S, d[S] = 0, cur[S] = h[S]; while(hh<=tt){ int t = q[hh++]; for (int i = h[t]; ~i;i=ne[i]){ int ver = e[i]; if(d[ver]==-1&&f[i]){ d[ver] = d[t] + 1; cur[ver] = h[ver]; if(ver==T) return 1; q[++tt] = ver; } } } return 0; } int find(int u,int limit){ if(u==T) return limit; int flow = 0; for (int i = cur[u]; ~i&&flow<limit;i=ne[i]){ cur[u] = i; int ver = e[i]; if(d[ver]==d[u]+1&&f[i]){ int t = find(ver, min(f[i], limit - flow)); if(!t) d[ver]=-1; f[i] -= t, f[i ^ 1] += t; flow += t; } } return flow; } int dinic(){ int r = 0, flow; while(bfs()){ while(flow=find(S,INF)) r += flow; } return r; } int main(){ memset(h, -1, sizeof(h)); cin >> m >> n; S = 0, T = n + 1; for (int i = 1; i <= m;i++) add(S, i, 1), add(i, S, 0); for (int i = m + 1; i <= n;i++) add(i, T, 1), add(T, i, 0); int a, b; while(cin>>a>>b,a!=-1) add(a, b, 1), add(b, a, 0); cout << dinic() << endl; for (int i = 0; i < idx;i+=2){ if(e[i]>m&&e[i]<=n&&!f[i]) cout << e[i ^ 1] << " " << e[i] << endl; } return 0; }
-
P3254 圆桌问题
-
思路:
- 建一个超级源点,向每个单位连一条为单位人数的边
- 建个单位向每个桌子连一条为1的边(同一个单位的人不能在同一个桌子上)
- 建一个超级汇点,每个桌子向汇点连一条为桌子容量的边
跑一遍最大流,如果最大流量等于所有单位人数之和,则存在解,否则无解。
输出方案:从每个单位出发的所有满流边指向的桌子就是该单位人员的安排情况。
-
代码:
here
#include <bits/stdc++.h> using namespace std; const int N = 430, M = (150 * 270 + N) * 2, INF = 1e8; int m, n, S, T; int h[N], e[M], f[M], ne[M], idx; int q[N], d[N], cur[N]; void add(int a, int b, int c){ e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ; } bool bfs(){ int hh = 0, tt = 0; memset(d, -1, sizeof d); q[0] = S, d[S] = 0, cur[S] = h[S]; while (hh <= tt){ int t = q[hh ++ ]; for (int i = h[t]; ~i; i = ne[i]){ int ver = e[i]; if (d[ver] == -1 && f[i]){ d[ver] = d[t] + 1; cur[ver] = h[ver]; if (ver == T) return true; q[ ++ tt] = ver; } } } return false; } int find(int u, int limit){ if (u == T) return limit; int flow = 0; for (int i = cur[u]; ~i && flow < limit; i = ne[i]){ cur[u] = i; int ver = e[i]; if (d[ver] == d[u] + 1 && f[i]){ int t = find(ver, min(f[i], limit - flow)); if (!t) d[ver] = -1; f[i] -= t, f[i ^ 1] += t, flow += t; } } return flow; } int dinic(){ int r = 0, flow; while (bfs()) while (flow = find(S, INF)) r += flow; return r; } int main(){ scanf("%d%d", &m, &n); S = 0, T = m + n + 1; memset(h, -1, sizeof h); int tot = 0; for (int i = 1; i <= m; i ++ ){ int c; scanf("%d", &c); add(S, i, c); tot += c; } for (int i = 1; i <= n; i ++ ){ int c; scanf("%d", &c); add(m + i, T, c); } for (int i = 1; i <= m; i ++ ) for (int j = 1; j <= n; j ++ ) add(i, m + j, 1); if (dinic() != tot) puts("0"); else{ puts("1"); for (int i = 1; i <= m; i ++ ){ for (int j = h[i]; ~j; j = ne[j]) if (e[j] > m && e[j] <= m + n && !f[j]) printf("%d ", e[j] - m); puts(""); } } return 0; }
-
上下界网络流
设 \(\operatorname{l}(u,\,v)\) 为 \(u \to v\) 的下界函数,特别的,当 \(u \to v \notin {\mathrm E}\) 时,\(\operatorname{l}(u,\,v) = 0\)。
定义图 \(\mathrm G = \langle \mathrm {V,\,E} \rangle\) 上的流函数 \(\operatorname{f} : \mathrm {V \times V} \to \mathbb R\),满足如下限制:
- 容量限制: 对于 \(\forall u,\,v \in {\mathrm V}\) 有 \(\operatorname{l}(u,\,v) \le \operatorname{f}(u,\,v) \le \operatorname{c}(u,\,v)\)。
- 流量守恒: 对于 \(\forall u \in {\mathrm{V}}-\{s,\,t\}\),要求:\(\begin{aligned} & \sum_{v \in {\mathrm{V}}}\operatorname{f}(u,\,v)=\sum_{v \in {\mathrm{V}}}\operatorname{f}(v,\,u) \end{aligned}\)
则称非负值 \(\operatorname{f}(u,\,v)\) 为图 \(\mathrm {G = \langle V,\,E \rangle}\) 的一个可行流。
一个流的流值 \(|f|\) 定义如下:
\[\begin{aligned} & |f| = \sum_{v \in {\mathrm{V}}}\operatorname{f}(s,\,v)-\sum_{v \in {\mathrm{V}}}\operatorname{f}(v,\,s) \end{aligned} \] -
无源汇上下界可行流
给定一个特殊点 \(s\) 和一个特殊点 \(t\),找出从 \(s\) 到 \(t\) 的一个流 \(\operatorname{f}(s,\,t)\),使得 \(|f|\) 的值最大。
无源汇上下界可行流就是没有源汇的情况下,求出图 \(\mathrm G = \langle \mathrm{V,\,E} \rangle\) 的一个可行流。
对于每条边,我们可以用上界减去下界得到差值网络。
建模方法如下:
- 设 \(\operatorname{in}(u)\) 为 \(u\) 的所有入边的容量的总和,\(\operatorname{out}(u)\) 为 \(u\) 的所有出边的容量的总和。
- 计算 \(w = \operatorname{in}(u)-\operatorname{out}(u)\)。
- 如果 \(w > 0\),则从超级源点 \(s\) 向 \(u\) 连一条容量为 \(w\) 的边。如果 \(w <0\),则从 \(u\) 向超级汇点 \(t\) 连容量为 \(-w\) 的边。
- 跑最大流,如果存在附加边没满流,则不存在可行流,否则 \(\mathrm maxflow\) 加上所有边的下界的和即为可行流的流值。
添加附加边维护了平衡条件,加上 下界网络就相当于得到了一个流量平衡的残余网络。
here
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 210, M = (10200 + N) * 2, INF = 1e8; int n, m, S, T; int h[N], e[M], f[M], l[M], ne[M], idx; int q[N], d[N], cur[N], A[N]; void add(int a, int b, int c, int d) { e[idx] = b, f[idx] = d - c, l[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ; } bool bfs() { int hh = 0, tt = 0; memset(d, -1, sizeof d); q[0] = S, d[S] = 0, cur[S] = h[S]; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; ~i; i = ne[i]) { int ver = e[i]; if (d[ver] == -1 && f[i]) { d[ver] = d[t] + 1; cur[ver] = h[ver]; if (ver == T) return true; q[ ++ tt] = ver; } } } return false; } int find(int u, int limit) { if (u == T) return limit; int flow = 0; for (int i = cur[u]; ~i && flow < limit; i = ne[i]) { cur[u] = i; int ver = e[i]; if (d[ver] == d[u] + 1 && f[i]) { int t = find(ver, min(f[i], limit - flow)); if (!t) d[ver] = -1; f[i] -= t, f[i ^ 1] += t, flow += t; } } return flow; } int dinic() { int r = 0, flow; while (bfs()) while (flow = find(S, INF)) r += flow; return r; } int main() { scanf("%d%d", &n, &m); S = 0, T = n + 1; memset(h, -1, sizeof h); for (int i = 0; i < m; i ++ ) { int a, b, c, d; scanf("%d%d%d%d", &a, &b, &c, &d); add(a, b, c, d); A[a] -= c, A[b] += c; } int tot = 0; for (int i = 1; i <= n; i ++ ) if (A[i] > 0) add(S, i, 0, A[i]), tot += A[i]; else if (A[i] < 0) add(i, T, 0, -A[i]); if (dinic() != tot) puts("NO"); else { puts("YES"); for (int i = 0; i < m * 2; i += 2) printf("%d\n", f[i ^ 1] + l[i]); } return 0; }
-
有源汇上下界可行流
即使添加了源点 \(s\) 和汇点 \(t\),我们仍然可以通过从 \(t\) 到 \(s\) 添加一条下界为 0、上界为 \(+\infty\) 的边,将问题转化为无源汇上下界可行流问题,直接求解。此时,可行流的容量等于从 \(t\) 到 \(s\) 的边的容量。
此时,原图的源点和汇点已视为普通点,而添加的超级源点和汇点分别为 \(s'\) 和 \(t'\)。可行流的容量就是从 \(t\) 到 \(s\) 的附加边的容量。
-
有源汇上下界最大流
在可行流的基础上,删除所有附加边后,使用原源汇节点求解一次最大流,再加上可行流的值即为最终答案。
因为如果存在可行流,原网络一定是平衡的,当前网络同样平衡,满足平衡条件,因此是正确的。
实际上,不必删除所有附加边,只需要删除 \(t\) 到 \(s\) 的附加边,因为这两个入度为 0 或出度为 0 的节点对最大流的计算没有影响。
here
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 210, M = (N + 10000) * 2, INF = 1e8; int n, m, S, T; int h[N], e[M], f[M], ne[M], idx; int q[N], d[N], cur[N], A[N]; void add(int a, int b, int c) { e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ; } bool bfs() { int hh = 0, tt = 0; memset(d, -1, sizeof d); q[0] = S, d[S] = 0, cur[S] = h[S]; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; ~i; i = ne[i]) { int ver = e[i]; if (d[ver] == -1 && f[i]) { d[ver] = d[t] + 1; cur[ver] = h[ver]; if (ver == T) return true; q[ ++ tt] = ver; } } } return false; } int find(int u, int limit) { if (u == T) return limit; int flow = 0; for (int i = cur[u]; ~i && flow < limit; i = ne[i]) { cur[u] = i; int ver = e[i]; if (d[ver] == d[u] + 1 && f[i]) { int t = find(ver, min(f[i], limit - flow)); if (!t) d[ver] = -1; f[i] -= t, f[i ^ 1] += t, flow += t; } } return flow; } int dinic() { int r = 0, flow; while (bfs()) while (flow = find(S, INF)) r += flow; return r; } int main() { int s, t; scanf("%d%d%d%d", &n, &m, &s, &t); S = 0, T = n + 1; memset(h, -1, sizeof h); while (m -- ) { int a, b, c, d; scanf("%d%d%d%d", &a, &b, &c, &d); add(a, b, d - c); A[a] -= c, A[b] += c; } int tot = 0; for (int i = 1; i <= n; i ++ ) if (A[i] > 0) add(S, i, A[i]), tot += A[i]; else if (A[i] < 0) add(i, T, -A[i]); add(t, s, INF); if (dinic() < tot) puts("No Solution"); else { int res = f[idx - 1]; S = s, T = t; f[idx - 1] = f[idx - 2] = 0; printf("%d\n", res + dinic()); } return 0; }
-
有源汇上下界最小流
和 有源汇上下界最小流 思路基本相同。
反向跑一边最大流,相当于流回去最大,那么就是最小流。
答案 \(=\) 可行流 \(-\) 反向最大流点击查看代码
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 50010, M = (N + 125003) * 2, INF = 2147483647; int n, m, S, T; int h[N], e[M], f[M], ne[M], idx; int q[N], d[N], cur[N], A[N]; void add(int a, int b, int c) { e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ; } bool bfs() { int hh = 0, tt = 0; memset(d, -1, sizeof d); q[0] = S, d[S] = 0, cur[S] = h[S]; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; ~i; i = ne[i]) { int ver = e[i]; if (d[ver] == -1 && f[i]) { d[ver] = d[t] + 1; cur[ver] = h[ver]; if (ver == T) return true; q[ ++ tt] = ver; } } } return false; } int find(int u, int limit) { if (u == T) return limit; int flow = 0; for (int i = cur[u]; ~i && flow < limit; i = ne[i]) { cur[u] = i; int ver = e[i]; if (d[ver] == d[u] + 1 && f[i]) { int t = find(ver, min(f[i], limit - flow)); if (!t) d[ver] = -1; f[i] -= t, f[i ^ 1] += t, flow += t; } } return flow; } int dinic() { int r = 0, flow; while (bfs()) while (flow = find(S, INF)) r += flow; return r; } int main() { int s, t; scanf("%d%d%d%d", &n, &m, &s, &t); S = 0, T = n + 1; memset(h, -1, sizeof h); while (m -- ) { int a, b, c, d; scanf("%d%d%d%d", &a, &b, &c, &d); add(a, b, d - c); A[a] -= c, A[b] += c; } int tot = 0; for (int i = 1; i <= n; i ++ ) if (A[i] > 0) add(S, i, A[i]), tot += A[i]; else if (A[i] < 0) add(i, T, -A[i]); add(t, s, INF); if (dinic() < tot) puts("No Solution"); else { int res = f[idx - 1]; S = t, T = s; f[idx - 1] = f[idx - 2] = 0; printf("%d\n", res - dinic()); } return 0; }
-
多源汇最大流
- 建立一个超级源点 \(S\),向每个 \(s\) 连一条容量为 $+\infty $ 的边。
- 建立一个超级汇点 \(T\),每个 \(t\) 向 \(T\) 连一条容量为 $+\infty $ 的边。
\(S \to T\) 的最大流即为原图多源汇最大流。
here
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 10010, M = (100000 + N) * 2, INF = 1e8; int n, m, S, T; int h[N], e[M], f[M], ne[M], idx; int q[N], d[N], cur[N]; void add(int a, int b, int c) { e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ; } bool bfs() { int hh = 0, tt = 0; memset(d, -1, sizeof d); q[0] = S, d[S] = 0, cur[S] = h[S]; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; ~i; i = ne[i]) { int ver = e[i]; if (d[ver] == -1 && f[i]) { d[ver] = d[t] + 1; cur[ver] = h[ver]; if (ver == T) return true; q[ ++ tt] = ver; } } } return false; } int find(int u, int limit) { if (u == T) return limit; int flow = 0; for (int i = cur[u]; ~i && flow < limit; i = ne[i]) { cur[u] = i; int ver = e[i]; if (d[ver] == d[u] + 1 && f[i]) { int t = find(ver, min(f[i], limit - flow)); if (!t) d[ver] = -1; f[i] -= t, f[i ^ 1] += t, flow += t; } } return flow; } int dinic() { int r = 0, flow; while (bfs()) while (flow = find(S, INF)) r += flow; return r; } int main() { int sc, tc; scanf("%d%d%d%d", &n, &m, &sc, &tc); S = 0, T = n + 1; memset(h, -1, sizeof h); while (sc -- ) { int x; scanf("%d", &x); add(S, x, INF); } while (tc -- ) { int x; scanf("%d", &x); add(x, T, INF); } while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } printf("%d\n", dinic()); return 0; }