网络流基础知识
一、最大流
网络流主要用来解决带权有向图,从源点到汇点能汇聚的最大权值。
最简单的网络流算法(naive,得到的不一定是最大流):
1.找到一条从源点到终点的简单路径,将这条路径上的最小值视为该条路径的流量,更新原图。
2.不断找从源点到终点的简单路径,进行更新,直到再也找不到,最终可以得到汇点的权值。
Ford–Fulkerson 增广
在naive算法中,每次找到一条简单路径更新后,同时沿着反向增加一条反悔路径,该反悔路径和原来的路径一样可以用来运输流量。
然后我们不停的找从源点到终点的简单路径,直到找不到为止。
可以证明该算法一定可以找到最大流,但是时间复杂度较高O(maxflow*edge)。
Edmonds–Karp 算法
在Ford–Fulkerson 增广的基础上,对时间复杂度进行优化。
每次找简单路径时把原图看成无权图,找最短路。
O(m^2*n)
Dinic 算法
BFS找阻塞流,每次更新
O(m*n^2)
二、最小割 Min-cut(边)
实则和最大流为同一问题。
两种理解:1)最小割=最大流 2)在余量图中找S能到达的所有点为S集,其余点为T集,在原图中将S集与T集做切割,所有S到T的边权和为最小割。
最小割点就相当于把点分成两个点连一条边,边被割了就相当于点被割了。
三、费用流
在最大流问题的基础上,给每一条边e一个单位费用ae,在最大化流量的前提下最小化费用。
解决:对于 EK 算法,把每次寻找边数最少的路径改成每次寻找费用和最小的路径即可。
网络流24题
最小费用最大流问题
以样例为例:
题目要求同时求出最小费用最大流和最大费用最小流,最大费用实际上就是将边权置为负值,跑最小费用最大流即可。
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 4050;
const int maxn = 1050;
typedef pair<int, int> P;
struct MCMF {
struct E {
int from, to, cap, v;
E() {}
E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
};
int n, m, s, t;
vector<E> edges;
vector<int> G[maxn];
bool inq[maxn];
int dis[maxn], pre[maxn], a[maxn];
void init(int _n, int _s, int _t) {
n = _n;
s = _s;
t = _t;
for (int i = 0; i <= n; i++)
G[i].clear();
edges.clear();
m = 0;
}
void add(int from, int to, int cap, int cost) {
edges.emplace_back(from, to, cap, cost);
edges.emplace_back(to, from, 0, -cost);
G[from].push_back(m++);
G[to].push_back(m++);
}
bool spfa() {
for (int i = 0; i <= n; i++) {
dis[i] = 1e9;
pre[i] = -1;
inq[i] = false;
}
dis[s] = 0, a[s] = 1e9, inq[s] = true;
queue<int> Q;
Q.push(s);
while (!Q.empty()) {
int u = Q.front();
Q.pop();
inq[u] = false;
for (int &idx: G[u]) {
E &e = edges[idx];
if (e.cap && dis[e.to] > dis[u] + e.v) {
dis[e.to] = dis[u] + e.v;
pre[e.to] = idx;
a[e.to] = min(a[u], e.cap);
if (!inq[e.to]) {
inq[e.to] = true;
Q.push(e.to);
}
}
}
}
return pre[t] != -1;
}
int solve() {
int flow = 0, cost = 0;
while (spfa()) {
flow += a[t];
cost += a[t] * dis[t];
int u = t;
while (u != s) {
edges[pre[u]].cap -= a[t];
edges[pre[u] ^ 1].cap += a[t];
u = edges[pre[u]].from;
}
}
return cost;
}
} f;
int a[MAXN], b[MAXN];
int c[MAXN][MAXN];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= m; i++)cin >> b[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> c[i][j];
}
}
f.s = 0;
f.t = n + m + 1;
f.init(n + m + 1, 0, n + m + 1);
for (int i = 1; i <= n; i++) {
f.add(0, i, a[i], 0);
}
for (int i = 1; i <= m; i++) {
f.add(n + i, n + m + 1, b[i], 0);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
f.add(i, j + n, INF, c[i][j]);
}
}
cout << f.solve() << '\n';
f.s = 0;
f.t = n + m + 1;
f.init(n + m + 1, 0, n + m + 1);
for (int i = 1; i <= n; i++) {
f.add(0, i, a[i], 0);
}
for (int i = 1; i <= m; i++) {
f.add(n + i, n + m + 1, b[i], 0);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
f.add(i, j + n, INF, -c[i][j]);
}
}
cout << -f.solve() << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
P1344 [USACO4.4] 追查坏牛奶 Pollutant Control
最小割板子题
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#define int long long
const int N = 1e4 + 5, M = 2e5 + 5;
const int INF = 0x3f3f3f3f3f3f3f;
int n, m, s, t, tot = 1, lnk[N], ter[M], nxt[M], val[M], dep[N], cur[N];
int vis[N];
void add(int u, int v, int w) {
ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, val[tot] = w;
}
void addedge(int u, int v, int w) { add(u, v, w), add(v, u, 0); }
int bfs(int s, int t) {
memset(dep, 0, sizeof(dep));
memcpy(cur, lnk, sizeof(lnk));
std::queue<int> q;
q.push(s), dep[s] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i];
if (val[i] && !dep[v]) q.push(v), dep[v] = dep[u] + 1;
}
}
return dep[t];
}
int dfs(int u, int t, int flow) {
if (u == t) return flow;
int ans = 0;
for (int &i = cur[u]; i && ans < flow; i = nxt[i]) {
int v = ter[i];
if (val[i] && dep[v] == dep[u] + 1) {
int x = dfs(v, t, std::min(val[i], flow - ans));
if (x) val[i] -= x, val[i ^ 1] += x, ans += x;
}
}
if (ans < flow) dep[u] = -1;
return ans;
}
int cnt = 0;
int dinic(int s, int t) {
int ans = 0;
while (bfs(s, t)) {
int x;
cnt++;
while ((x = dfs(s, t, 1 << 30))) {
ans += x;
}
}
return ans;
}
int dfs(int u) {
vis[u] = 1;
int ans = 0;
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i];
if (!vis[v] && val[i]) {
ans += dfs(v);
} else if (!vis[v])ans++;
}
return ans;
}
signed main() {
scanf("%lld%lld", &n, &m);
s = 1, t = n;
while (m--) {
int u, v, w;
scanf("%lld%lld%lld", &u, &v, &w);
addedge(u,v,w*N+1);
}
int ANS = dinic(s, t);
printf("%lld %lld", ANS / N, ANS % N);
return 0;
}
struct edge {
int u, v, cap, flow;
edge() {}
edge(int u, int v, int cap, int flow) : u(u), v(v), cap(cap), flow(flow) {}
} eg[maxm << 1];
int tot, dis[maxn << 1], cur[maxn << 1];
vector<int> tab[maxn << 1];
void addedge(int u, int v, int cap) {
tab[u].push_back(tot);
eg[tot++] = edge(u, v, cap, 0);
tab[v].push_back(tot);
eg[tot++] = edge(v, u, 0, 0);
}
int bfs(int s, int t) {
queue<int> q;
q.push(s);
memset(dis, INF, sizeof dis);
dis[s] = 0;
while (!q.empty()) {
int h = q.front(), i;
q.pop();
for (i = 0; i < tab[h].size(); i++) {
edge &e = eg[tab[h][i]];
if (e.cap > e.flow && dis[e.v] == INF) {
dis[e.v] = dis[h] + 1;
q.push(e.v);
}
}
}
return dis[t] < INF;
}
int dfs(int x, int maxflow, int s, int t) {
if (x == t || maxflow == 0)
return maxflow;
int flow = 0, i, f;
for (i = cur[x]; i < tab[x].size(); i++) {
cur[x] = i;
edge &e = eg[tab[x][i]];
if (dis[e.v] == dis[x] + 1 && (f = dfs(e.v, min(maxflow, e.cap - e.flow), s, t)) > 0) {
e.flow += f;
eg[tab[x][i] ^ 1].flow -= f;
flow += f;
maxflow -= f;
if (maxflow == 0)
break;
}
}
return flow;
}
int dinic(int s, int t) {
int flow = 0;
while (bfs(s, t)) {
memset(cur, 0, sizeof(cur));
flow += dfs(s, INF, s, t);
}
return flow;
}
int main() {
scanf("%d%d", &n, &m);
int u, v;
while (scanf("%d%d", &u, &v)) {
if (u == -1 && v == -1)break;
addedge(u, v, 1);
}
for (int i = 1; i <= n; i++) {
addedge(0, i, 1);
}
for (int i = n + 1; i <= m; i++) {
addedge(i, m + 1, 1);
}
int flow = dinic(0, m + 1);
printf("%d\n", flow);
for (int i = 1; i <= tot; i += 2) {
if (eg[i].u <= m && eg[i].u > n && eg[i].v <= n && eg[i].flow == -1)
printf("%d %d\n", eg[i].v, eg[i].u);
}
return 0;
}
按照题意建图即可。
最小费用最大流,中间相邻点建INF容量,单位费用为1的边。
P2765 魔术球问题
假设有 n 根柱子,现要按下述规则在这 n 根柱子中依次放入编号为 1,2,3,...的球“
-
每次只能在某根柱子的最上面放球。
-
同一根柱子中,任何 2 个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在 n 根柱子上最多能放多少个球。例如,在 4根柱子上最多可放 11 个球。
对于给定的 n,计算在 n根柱子上最多能放多少个球。
最大边覆盖=点数-最大匹配,跑dinic的最大流即可求出最大匹配。
同时将球拆点,与源点汇点相连,同时与它可以匹配的球相连跑最大流即可。