网络流笔记
Dinic 算法求最大流
算法思想
首先用bfs通过网络流(残量)将图分层, 然后dfs搜索分层图,一次dfs可以搜出多条可行流, 但是这些可行流所经过的边数目都是相等的。
时间复杂度为\(\theta(n^2m)\)
但是实际上在问题中,及少可以卡的满。 而且很多网络流问题是二分图, 这时时间复杂度约为\(\theta(n\sqrt m)\)
代码实现
code
namespace flow{
int dep[N + 5], cur[N + 5], h[N + 5], S, T;
struct Edge{
int v, nxt, val;
}e[M + 5];
int tot;
void add(int x, int y, int w) {
e[++tot] = Edge{y, h[x], w};
h[x] = tot;
e[++tot] = Edge{x, h[y], 0};
h[y] = tot;
}
void init() {
flow::S = n + 1; flow::T = n + 2;
tot = 1;
memset(h, 0, sizeof(h));
}
queue<int > q;
int bfs() {
memset(dep, 0, sizeof(dep)); q.push(S); dep[S] = 1;
while(!q.empty()) {
int u = q.front(); q.pop(); cur[u] = h[u];
for(int i = h[u], v; i; i = e[i].nxt)
if (e[i].val && !dep[v = e[i].v]) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
return dep[T];
}
int dfs(int u, int flow) {
if (u == T || !flow)
return flow;
int ret = 0;
for(int &i = cur[u]; i; i = e[i].nxt) {
int v = e[i].v, d;
if ((dep[v] == dep[u] + 1) && (d = dfs(v, min(flow - ret, e[i].val)))) {
e[i].val -= d; e[i ^ 1].val += d; ret += d;
if (ret == flow) return flow;
}
}
return ret;
}
int dinic() {
int ans = 0;
while(bfs())
ans += dfs(S, inf);
return ans;
}
};
MCMF 费用流(spfa)
算法思想
因为要求在流量最大的情况下要求费用最小, 所以考虑按照费用从小到大增广每一条可行流, 显然一条可行流的费用是路径上所有边的权值和。 所以我们可以采用SPFA算法。在SPFA的过程中记下路径, 然后依次对路径所有边的残量进行操作。 其实和EK算法很像,一次增广一条路径。
代码实现
code
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <bits/stdc++.h>
const int N = 1e5;
using namespace std;
int n, m, s, t, u, v, w, c;
void read(int &x) {
x = 0; int w = 1; char c = getchar(); for(; c < '0' || c > '9'; c = getchar()) if(c == '-') w = -1;
for(; c <= '9' && c >= '0'; c = getchar()) x = x * 10 + c - '0'; x *= w;
}
struct Edge{
int v, nxt, val, dis;
}e[N + 5];
int h[N + 5], tot, inq[N + 5], dis[N + 5], flow[N + 5], pre[N + 5];
int mf, mc, bk[N + 5];
void add(int x, int y, int z, int w) {
e[++tot] = Edge{y, h[x], z, w};
h[x] = tot;
}
queue<int > q;
int spfa() {
memset(inq, 0, sizeof(inq)); memset(dis, 0x7f, sizeof(dis)); memset(flow, 0x7f, sizeof(flow));
dis[s] = 0; inq[s] = 1; pre[t] = -1; q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop(); inq[u] = 0;
// cout << u << endl;
for(int i = h[u], v; i; i = e[i].nxt)
if (e[i].val && dis[u] + e[i].dis < dis[v = e[i].v]) {
dis[v] = dis[u] + e[i].dis;
flow[v] = min(flow[u], e[i].val);
pre[v] = u; bk[v] = i;
if (!inq[v]) {
q.push(v);
// cout << u << ' ' << v << endl;
inq[v] = 1;
}
}
}
// cout << pre[t] << endl;
return pre[t] != -1;
}
int main() {
// freopen("t.in", "r", stdin);
read(n); read(m); read(s); read(t); tot = 1;
for(int i = 1; i <= m; ++i) {
read(u); read(v); read(w); read(c);
add(u, v, w, c); add(v, u, 0, -c);
}
while(spfa()) {
// cout << flow[t] << endl;
mf += flow[t]; mc += flow[t] * dis[t];
int now = t;
while(now != s) {
e[bk[now]].val -= flow[t]; e[bk[now]^1].val += flow[t];
now = pre[now];
}
}
printf("%d %d\n", mf, mc);
return 0;
}
有上下界的最大流
算法思想
一个自然的想法就用 上界-下界 作为新网络的容量, 但是这样实际(加上默认的下界流量)不符合流量平衡。 然而在网络流中源点和汇点是可以不符合流量平衡的。 考虑在网络外再建一个源点和一个汇点, 定义 \(in_i\)表示所有\(v = i\)的边\((u,v)\)的下界和, \(out_i\)表示所有\(u = i\)的边\((u,v)\)的下界和。 然后对于所有\(in_i > out_i\)的点,从新建的\(S\)向\(i\)引一条容量\(in_i - out_i\)的边, 对于\(in_i < out_i\)的边, 向新建的汇点\(T\)建一条\(out_i - in_i\)的边。 同时为了流量平衡从原来\(t\)向原来\(s\)连一条容量为\(\infty\)的边。
然后我们可以跑一遍最大流记为\(k\), 如果\(k\)此时不等于从\(S\)出的容量和, 显然无法满足下界,输出无解。
如果等于, 记加入的\(t -> s\)的反向边的容量为flow1, 所以我们可以去掉\(S\)和\(T\)以及\((t,s)\)的边, 然后在原来残量网络上再跑一次最大流记为\(flow2\)
那么答案等于\(flow1 + flow2\)
注意这里\(flow1\)不等于从\(S\)出发的容量(这样写,在洛谷能过,但FZOJ和LOJ过不了), 因为从\(S\)出发的容量只是使其容量平衡的, 只有经过\(t\)然后流会\(s\)绕一圈才是从\(s\)到\(t\)的真实流量。
代码实现
code
#include <stdio.h>
#include <queue>
#include <iostream>
#include <algorithm>
#include <bits/stdc++.h>
#define LL long long
const int N = 400, M = 2e5;
#define inf 100000000
using namespace std;
#define L(i, s, t) for(int i = s; i <= t; ++i)
#define R(i, t, s) for(int i = t; i >= s; --i)
int n, m, s, t, S, T, h[N + 5], d[N + 5];
int dep[N + 5], cur[N + 5];
struct Edge{
int v, nxt, val;
}e[M + 5];
int tot;
void add(int x, int y, int w) {
e[++tot] = Edge{y, h[x], w};
h[x] = tot;
e[++tot] = Edge{x, h[y], 0};
h[y] = tot;
}
queue<int > q;
int bfs() {
memset(dep, 0, sizeof(dep)); q.push(S); dep[S] = 1;
while(!q.empty()) {
int u = q.front(); q.pop(); cur[u] = h[u];
for(int i = h[u], v; i; i = e[i].nxt)
if (e[i].val && !dep[v = e[i].v]) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
return dep[T];
}
int dfs(int u, int flow) {
if (u == T || !flow)
return flow;
int ret = 0;
for(int &i = cur[u]; i; i = e[i].nxt) {
int v = e[i].v, d;
if ((dep[v] == dep[u] + 1) && (d = dfs(v, min(flow - ret, e[i].val)))) {
e[i].val -= d; e[i ^ 1].val += d; ret += d;
if (ret == flow) return flow;
}
}
return ret;
}
int dinic() {
int ans = 0;
// cout << "what" << endl;
while(bfs())
ans += dfs(S, inf);
return ans;
}
int main() {
// freopen("t.in", "r", stdin);
tot = 1;
scanf("%d%d%d%d", &n, &m, &s, &t); S = n + 1; T = n + 2; int sta = 0;
L(i, 1, m) {
int u, v, low, upp;
scanf("%d%d%d%d", &u, &v, &low, &upp);
add(u, v, upp - low);
d[v] += low; d[u] -= low;
}
L(i, 1, T)
if (d[i] > 0) add(S, i, d[i]), sta += d[i];
else add(i, T, -d[i]);
add(t, s, inf);
int ans = dinic();
// cout << e[tot - 1].val << endl;
if (ans != sta)printf("Impossble\n");
else {
ans = e[tot].val;
e[tot].val = 0; e[tot - 1].val = 0;
S = s; T = t; ans += dinic();
printf("%d\n", ans);
}
return 0;
}
有上下界的最小流
算法思想
基本等于上面的的 有上下界的最大流, 唯一不同的是 找出可行流后要想办法减流, 删去\((t,s)\)边, 然后\(ans\)减去从\(t\)到\(s\)的最大流,相当于退流。
代码实现
code
#include <stdio.h>
#include <queue>
#include <iostream>
#include <algorithm>
#include <bits/stdc++.h>
#define LL long long
const int N = 6e4, M = 5e5;
#define inf 2147483647
using namespace std;
#define L(i, s, t) for(int i = s; i <= t; ++i)
#define R(i, t, s) for(int i = t; i >= s; --i)
int n, m, s, t, S, T, h[N + 5], d[N + 5];
int dep[N + 5], cur[N + 5];
struct Edge{
int v, nxt, val;
}e[M + 5];
int tot;
void add(int x, int y, int w) {
e[++tot] = Edge{y, h[x], w};
h[x] = tot;
e[++tot] = Edge{x, h[y], 0};
h[y] = tot;
}
queue<int > q;
int bfs() {
memset(dep, 0, sizeof(dep)); q.push(S); dep[S] = 1;
while(!q.empty()) {
int u = q.front(); q.pop(); cur[u] = h[u];
for(int i = h[u], v; i; i = e[i].nxt)
if (e[i].val && !dep[v = e[i].v]) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
return dep[T];
}
int dfs(int u, int flow) {
if (u == T || !flow)
return flow;
int ret = 0;
for(int &i = cur[u]; i; i = e[i].nxt) {
int v = e[i].v, d;
if ((dep[v] == dep[u] + 1) && (d = dfs(v, min(flow - ret, e[i].val)))) {
e[i].val -= d; e[i ^ 1].val += d; ret += d;
if (ret == flow) return flow;
}
}
return ret;
}
int dinic() {
int ans = 0;
// cout << "what" << endl;
while(bfs())
ans += dfs(S, inf);
return ans;
}
int main() {
// freopen("t.in", "r", stdin);
tot = 1;
scanf("%d%d%d%d", &n, &m, &s, &t); S = n + 1; T = n + 2; int sta = 0;
L(i, 1, m) {
int u, v, low, upp;
scanf("%d%d%d%d", &u, &v, &low, &upp);
add(u, v, upp - low);
d[v] += low; d[u] -= low;
}
L(i, 1, T)
if (d[i] > 0) add(S, i, d[i]), sta += d[i];
else add(i, T, -d[i]);
add(t, s, inf);
int ans = dinic();
// cout << e[tot - 1].val << endl;
if (ans != sta)printf("please go home to sleep\n");
else {
ans = e[tot].val;
e[tot].val = 0; e[tot - 1].val = 0;
S = t; T = s; ans -= dinic();
printf("%d\n", ans);
}
return 0;
}
待补