「学习笔记」网络流

网络流大杂烩

最大流

dinic,每次找增广路

优化:多路增广、当前弧优化

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
#define pb push_back

using namespace std;

namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}

template <typename T, typename ...Args>
void read(T &x, Args &...args) {read(x), read(args...);}

template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;

const int N = 205;
const int M = 5005;

int n, m, s, t;
struct Edge {
int v;
ll w;
int nxt;
} e[M << 1];
int head[N], tot = 1;

void link(int u, int v, int w)
{
e[++tot] = (Edge) {v, w, head[u]};
head[u] = tot;
}

queue <int> que;
int dep[N], cur[N];

bool bfs()
{
memset(dep, 0, sizeof(dep));
dep[s] = 1, que.push(s);
while(!que.empty())
{
int u = que.front(); que.pop();
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v;
if(e[i].w && !dep[v])
{
dep[v] = dep[u] + 1;
que.push(v);
}
}
}
if(!dep[t]) return 0;
for(int i = 1; i <= n; ++ i) cur[i] = head[i];
return 1;
}

ll dfs(int u, ll in)
{
if(u == t || !in) return in;
ll out = 0;
for(int i = cur[u]; i; i = e[i].nxt)
{
int v = e[i].v; cur[u] = i;
if(e[i].w && dep[v] == dep[u] + 1)
{
ll flow = dfs(v, min(in, e[i].w));
e[i].w -= flow, e[i ^ 1].w += flow;
in -= flow, out += flow;
if(!in) break;
}
}
return out;
}

int main()
{
read(n, m, s, t);
for(int i = 1, u, v, w; i <= m; i++)
{
read(u, v, w);
link(u, v, w), link(v, u, 0);
}
ll ans = 0;
while(bfs()) ans += dfs(s, 1e18);
write(ans), pc('\n');
return 0;
}
// A.S.

费用流

将最大流的 bfs 改为 spfa 求最短路图,然后找增广路

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
#define pb push_back
#define INF 0x3f3f3f3f

using namespace std;

namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}

template <typename T, typename ...Args>
void read(T &x, Args &...args) {read(x), read(args...);}

template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;

const int N = 5e3 + 5;
const int M = 5e4 + 5;

int n, m, s, t;
struct Edge
{
int v, w, c, nxt;
} e[M << 1];
int head[N], tot = 1, cur[N];

void link(int u, int v, int w, int c)
{
e[++tot] = (Edge) {v, w, c, head[u]};
head[u] = tot;
}

int dis[N];
bool vis[N];
queue <int> que;

bool spfa()
{
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[s] = 0; que.push(s); vis[s] = 1;
while(!que.empty())
{
int u = que.front();
que.pop(); vis[u] = 0;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v;
if(!e[i].w || dis[v] <= dis[u] + e[i].c) continue;
dis[v] = dis[u] + e[i].c;
if(!vis[v]) que.push(v), vis[v] = 1;
}
}
if(dis[t] == INF) return false;
for(int i = 1; i <= n; i++) cur[i] = head[i];
return true;
}

int dfs(int u, int in)
{
if(u == t || !in) return in;
int out = 0; vis[u] = 1;
for(int i = cur[u]; i; i = e[i].nxt)
{
int v = e[i].v; cur[u] = i;
if(vis[v] || !e[i].w || dis[v] != dis[u] + e[i].c) continue;
int flow = dfs(v, min(in, e[i].w));
e[i].w -= flow, e[i ^ 1].w += flow;
in -= flow, out += flow;
if(!in) break;
}
return out;
}

void solve()
{
int maxflow = 0, mincost = 0;
while(spfa())
{
int flow = dfs(s, INF);
maxflow += flow;
mincost += flow * dis[t];
}
write(maxflow), pc(' '), write(mincost), pc('\n');
return;
}

int main()
{
read(n, m, s, t);
for(int i = 1, u, v, w, c; i <= m; i++)
{
read(u, v, w, c);
link(u, v, w, c), link(v, u, 0, -c);
}
return solve(), 0;
}
// A.S.

无源汇有上下界可行流

在初始流的基础上不断添加额外流量,使流量守恒。

初始流就是流量的下界,然后考虑在残量网络上添加流量

对每个点,设 ww 表示入流减出流的差,分类讨论

  • w>0w>0,表示流入量大于流出量,此时需要给多出来的流入量找到一个来源,所以从超级源点向当前点连一条流量为 ww 的边。

  • w<0w<0,同理,从当前点向超级汇点连一条流量为 w-w 的边。

  • w=0w=0,流量已经守恒,不用管。

然后判断是否可行有两种方法:

  1. 判断超级源点连出去的边是否都已经满流
  2. 判断超级源点到超级汇点的最大流是否等于超级源点连出去的总流量
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
#define pb push_back

using namespace std;

namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}

template <typename T, typename ...Args>
void read(T &x, Args &...args) {read(x), read(args...);}

template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;

const int N = 205;
const int M = 1e5;
const int inf = 1e9;

int n, m, s, t;
struct Edge {
int v, w, nxt;
} e[M];
int head[N], tot = 1;
int in[N], out[N], low[M], id[M];

void link(int u, int v, int w)
{
e[++tot] = {v, w, head[u]}, head[u] = tot;
e[++tot] = {u, 0, head[v]}, head[v] = tot;
}

queue <int> que;
int dep[N], cur[N];

bool bfs()
{
memset(dep, 0, sizeof(dep));
dep[s] = 1, que.push(s);
while(!que.empty())
{
int u = que.front(); que.pop();
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v;
if(!e[i].w || dep[v]) continue;
dep[v] = dep[u] + 1;
que.push(v);
}
}
for(int i = 0; i <= t; i++) cur[i] = head[i];
return dep[t];
}

int dfs(int u, int in)
{
if(u == t || !in) return in;
int out = 0;
for(int &i = cur[u]; i; i = e[i].nxt)
{
int v = e[i].v;
if(!e[i].w || dep[v] != dep[u] + 1) continue;
int f = dfs(v, min(in, e[i].w));
e[i].w -= f, e[i^1].w += f;
in -= f, out += f;
if(!in) break;
}
return out;
}

int dinic()
{
int res = 0;
while(bfs()) res += dfs(s, inf);
return res;
}

int main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
read(n, m); s = 0, t = n + 1;
for(int i = 1; i <= m; i++)
{
int u, v, up; read(u, v, low[i], up);
link(u, v, up - low[i]); id[i] = tot;
out[u] += low[i], in[v] += low[i];
}
int sum = 0;
for(int i = 1; i <= n; i++)
{
int w = in[i] - out[i];
if(w > 0) link(s, i, w), sum += w;
if(w < 0) link(i, t, -w);
}
// dinic();
// for(int i = head[s]; i; i = e[i].nxt)
// if(e[i].w) return puts("NO"), 0;
if(dinic() != sum) return puts("NO"), 0;
puts("YES");
for(int i = 1; i <= m; i++)
write(low[i] + e[id[i]].w), pc('\n');
return 0;
}
// A.S.

有源汇有上下界最大流

有源汇,我们要保证流量守恒,所以可以从汇点向源点连一条正无穷的边,然后从源点到汇点的流量就可以跑满了。

最大流即为可行流加上原图的残量网络上的最大流。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
#define pb push_back

using namespace std;

namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}

template <typename T, typename ...Args>
void read(T &x, Args &...args) {read(x), read(args...);}

template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;

const int N = 205;
const int M = 1e6;
const int inf = 1e9;

int n, m, s, t, ss, tt;
int in[N], out[N], sum;

namespace Flow
{
int S, T;
struct Edge {
int v, w, nxt;
} e[M];
int head[N], tot = 1;

void link(int u, int v, int w)
{
e[++tot] = {v, w, head[u]}, head[u] = tot;
e[++tot] = {u, 0, head[v]}, head[v] = tot;
}

queue <int> que;
int dep[N], cur[N];

bool bfs()
{
memset(dep, 0, sizeof(dep));
dep[S] = 1, que.push(S);
while(!que.empty())
{
int u = que.front(); que.pop();
for(int i = head[u], v; i; i = e[i].nxt)
{
if(!e[i].w || dep[v = e[i].v]) continue;
dep[v] = dep[u] + 1;
que.push(v);
}
}
for(int i = 1; i <= n + 2; i++) cur[i] = head[i];
return dep[T];
}

int dfs(int u, int in)
{
if(u == T || !in) return in;
int out = 0;
for(int& i = cur[u], v; i; i = e[i].nxt)
{
if(!e[i].w || dep[v = e[i].v] != dep[u] + 1) continue;
int f = dfs(v, min(in, e[i].w));
e[i].w -= f, e[i^1].w += f;
in -= f, out += f;
if(!in) break;
}
return out;
}

int dinic()
{
int res = 0;
while(bfs()) res += dfs(S, inf);
return res;
}
}
using Flow :: link;
using Flow :: dinic;
using namespace Flow;

int main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
read(n, m, s, t);
for(int i = 1; i <= m; i++)
{
int u, v, low, up; read(u, v, low, up);
link(u, v, up - low);
in[v] += low, out[u] += low;
}
ss = n + 1, tt = n + 2;
for(int i = 1; i <= n; i++)
{
int w = in[i] - out[i];
if(w > 0) link(ss, i, w), sum += w;
if(w < 0) link(i, tt, -w);
}
link(t, s, inf);
S = ss, T = tt; int flow = dinic();
if(flow != sum) return puts("please go home to sleep"), 0;
flow = e[tot].w;
e[tot].w = e[tot ^ 1].w = 0;
S = s, T = t; flow += dinic();
write(flow), pc('\n');
return 0;
}
// A.S.

有源汇有上下界最小流

和最大流一样,在可行流的基础上,退回一部分流量

具体而言,就是在残量网络上从汇点到源点的最大流,也就是反向边的最大流,但是我们新建的超级源汇连的边会产生影响,需要把这两个点删掉。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
#define pb push_back

using namespace std;

namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}

template <typename T, typename ...Args>
void read(T &x, Args &...args) {read(x), read(args...);}

template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;

#define inf 1e18
#define int long long

const int N = 5e4 + 10;
const int M = 5e5;

int n, m, s, t, ss, tt;
int in[N], out[N], sum;

int S, T;
struct Edge {
int v, w, nxt;
} e[M];
int head[N], tot = 1;

void link(int u, int v, int w)
{
e[++tot] = {v, w, head[u]}, head[u] = tot;
e[++tot] = {u, 0, head[v]}, head[v] = tot;
}

queue <int> que;
int dep[N], cur[N];

bool bfs()
{
memset(dep, 0, sizeof(dep));
dep[S] = 1, que.push(S);
while(!que.empty())
{
int u = que.front(); que.pop();
for(int i = head[u], v; i; i = e[i].nxt)
{
if(!e[i].w || dep[v = e[i].v]) continue;
dep[v] = dep[u] + 1;
que.push(v);
}
}
for(int i = 0; i <= n + 2; i++) cur[i] = head[i];
return dep[T];
}

int dfs(int u, int in)
{
if(u == T || !in) return in;
int out = 0;
for(int& i = cur[u], v; i; i = e[i].nxt)
{
if(!e[i].w || dep[v = e[i].v] != dep[u] + 1) continue;
int f = dfs(v, min(in, e[i].w));
e[i].w -= f, e[i^1].w += f;
in -= f, out += f;
if(!in) break;
}
return out;
}

int dinic()
{
int res = 0;
while(bfs()) res += dfs(S, inf);
return res;
}

signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
read(n, m, s, t);
for(int i = 1; i <= m; i++)
{
int u, v, low, up; read(u, v, low, up);
link(u, v, up - low);
in[v] += low, out[u] += low;
}
ss = n + 1, tt = n + 2;
for(int i = 1; i <= n; i++)
{
int w = in[i] - out[i];
if(w > 0) link(ss, i, w), sum += w;
if(w < 0) link(i, tt, -w);
}
link(t, s, inf);
S = ss, T = tt; int flow = 0;
while(bfs()) flow += dfs(S, inf);
if(flow != sum) return puts("please go home to sleep"), 0;
flow = e[tot].w;
e[tot].w = e[tot ^ 1].w = 0;
S = t, T = s;
while(bfs()) dep[ss] = dep[tt] = 0, flow -= dfs(S, inf);
write(flow), pc('\n');
return 0;
}
// A.S.

最大权闭合子图

新建超级源汇

将正权的点与源点连边,流量为点权,负权的点与汇点连边,流量为点权的绝对值,再把原来的边连上,流量为正无穷

最后答案即为正权的点权和减去最小割

因为中间的边流量都是正无穷,所以最小割只能割源汇点连出去的边,也就是相当于对每个点进行选择。