网络流

1. 最大匹配 = 最小点覆盖。

2. 最小割 = 最大匹配

3. 二分图最大独立集 = 点数 - 最大匹配。

4. 二分图最大团 = 补图最大独立集。

5. DAG 最小链覆盖 = n - 拆点后二分图最大匹配。

6. 最大权闭合子图 = 正权和 - 最小割

网络流 24 题

这个其实不是网络流…

直接设 kik_i 表示 iii+1i + 1 搬运量,然后可以得出等式 ki+ai1ai=avgk_i+a_{i-1}-a_i=avg,推下式子即可。

时间复杂度:O(nlogn)O(n\log n)

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
#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 105;

int n;
int a[kMaxN], c[kMaxN];

void dickdreamer() {
std::cin >> n;
int sum = 0, avg;
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
sum += a[i];
}
avg = sum / n;
std::vector<int> v = {0};
int sc = 0;
for (int i = 2; i <= n; ++i) {
c[i] = a[i] - avg;
sc += c[i];
v.emplace_back(sc);
}
std::sort(v.begin(), v.end());
int x = v[v.size() / 2], ans = 0;
for (auto y : v)
ans += abs(y - x);
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

二分图最大匹配板子。


直接把试题放左边,类型放右边,让每个试题与它所属的类型连一条流量为 11 的边,源点向每个试题连流量为 11 的边,每个类型向汇点连流量为它所需个数的边,跑最大流即可。

输出方案就在残余网络上找满流的边即可。

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
#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 2e3 + 5, kMaxM = 3e4 + 5, kInf = 0x3f3f3f3f;

struct Edge {
int v, w, pre;
} e[kMaxM];

int k, n, m, s, t, tot = 1;
int tail[kMaxN], cur[kMaxN], idx[kMaxN][kMaxN], dep[kMaxN];
bool vis[kMaxN];

void adde(int u, int v, int w) { e[++tot] = {v, w, tail[u]}, tail[u] = idx[u][v] = tot; }
void add(int u, int v, int w) { adde(u, v, w), adde(v, u, 0); }

bool bfs() {
for (int i = 1; i <= t; ++i)
cur[i] = tail[i], vis[i] = 0, dep[i] = kInf;
std::queue<int> q;
q.emplace(s), dep[s] = 0, vis[s] = 1;
for (; !q.empty();) {
int u = q.front();
q.pop();
for (int i = tail[u]; i; i = e[i].pre) {
int v = e[i].v;
if (!e[i].w || vis[v]) continue;
dep[v] = dep[u] + 1, q.emplace(v), vis[v] = 1;
}
}
return vis[t];
}

int dfs(int u, int lim) {
if (u == t || !lim) return lim;
int flow = 0;
for (int &i = cur[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].w;
if (w && dep[v] == dep[u] + 1) {
int fl = dfs(v, std::min(lim, w));
if (!fl) dep[v] = kInf;
e[i].w -= fl, e[i ^ 1].w += fl;
lim -= fl, flow += fl;
if (!lim) break;
}
}
return flow;
}

void dickdreamer() {
std::cin >> k >> n;
s = n + k + 1, t = n + k + 2;
for (int i = 1; i <= k; ++i) {
int x;
std::cin >> x;
add(i + n, t, x);
m += x;
}
for (int i = 1; i <= n; ++i) {
int p, x;
std::cin >> p;
for (; p; --p) {
std::cin >> x;
add(i, x + n, kInf);
}
add(s, i, 1);
}
int ans = 0;
for (; bfs(); ans += dfs(s, kInf)) {}
if (ans < m) {
std::cout << "No Solution!\n";
return;
}
for (int i = 1; i <= k; ++i) {
std::cout << i << ": ";
for (int j = 1; j <= n; ++j)
if (e[idx[i + n][j]].w)
std::cout << j << ' ';
std::cout << '\n';
}
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

费用流板子


每个代表向每个餐桌连流量为 11 的边然后跑最大流即可。


最大权闭合子图板子。

考虑让答案为总和-最小割。

源点向所有实验 ii 连流量为其奖金的边,实验 ii 向它所有所需仪器连流量为 ++\infty 表示不能断这条边,然后仪器 jj 向汇点连流量为其费用的边。

那么由于断不了中间的边,所以对于实验 ii,要让它不连通,要么断掉源点连它的边,表示不做这个实验。要么断掉它所有所需仪器向汇点连的边,表示要买这些仪器。

会发现答案 = 奖金和 - 左边割 - 右边割 = 奖金和 - 最小割。

跑最大流即可。

输出方案就找在残余网络上和源点连通的实验说明它没割,和源点连通的仪器说明割了。

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
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 105, kMaxM = 1e5 + 5, kInf = 1e9;

struct Edge {
int v, w, pre;
} e[kMaxM];

int n, m, s, t, sum, tot = 1;
int tail[kMaxN], cur[kMaxN], idx[kMaxN][kMaxN], dep[kMaxN];
bool vis[kMaxN];

void adde(int u, int v, int w) { e[++tot] = {v, w, tail[u]}, tail[u] = idx[u][v] = tot; }
void add(int u, int v, int w) { adde(u, v, w), adde(v, u, 0); }

bool bfs() {
for (int i = 1; i <= t; ++i)
cur[i] = tail[i], vis[i] = 0, dep[i] = kInf;
std::queue<int> q;
q.emplace(s), dep[s] = 0, vis[s] = 1;
for (; !q.empty();) {
int u = q.front();
q.pop();
for (int i = tail[u]; i; i = e[i].pre) {
int v = e[i].v;
if (!e[i].w || vis[v]) continue;
dep[v] = dep[u] + 1, q.emplace(v), vis[v] = 1;
}
}
return vis[t];
}

int dfs(int u, int lim) {
if (u == t || !lim) return lim;
int flow = 0;
for (int &i = cur[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].w;
if (w && dep[v] == dep[u] + 1) {
int fl = dfs(v, std::min(lim, w));
if (!fl) dep[v] = kInf;
e[i].w -= fl, e[i ^ 1].w += fl;
lim -= fl, flow += fl;
if (!lim) break;
}
}
return flow;
}

void dickdreamer() {
std::cin >> n >> m;
s = n + m + 1, t = n + m + 2;
for (int i = 1; i <= n; ++i) {
int num = 0, fl = 0;
std::string str;
getline(std::cin, str);
while (!str.size()) getline(std::cin, str);
str += '\n';
for (auto c : str) {
if (isdigit(c)) {
num = 10 * num + c - '0';
} else {
if (!fl) add(s, i, num), sum += num, fl = 1;
else add(i, num + n, kInf);
num = 0;
}
}
}
for (int i = 1; i <= m; ++i) {
int val;
std::cin >> val;
add(i + n, t, val);
}
int ans = 0;
for (; bfs(); ans += dfs(s, kInf)) {}
for (int i = 1; i <= n; ++i)
if (dep[i] <= t)
std::cout << i << ' ';
std::cout << '\n';
for (int i = 1; i <= m; ++i)
if (dep[i + n] <= t)
std::cout << i << ' ';
std::cout << '\n' << sum - ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

最大独立集板子。

观察可知对棋盘黑白染色后,两个可以互相吃掉的马颜色一定不同,所以把黑的放左边,白的放右边,能互相吃掉的就连边,答案就是 n2m最大流n^2-m-最大流

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
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 4e4 + 5, kMaxM = 1e6 + 5, kInf = 1e9;
const int kD[][2] = {{-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}, {-1, -2}, {-2, -1}};

struct Edge {
int v, w, pre;
} e[kMaxM];

int n, m, s, t, tot = 1;
int cnt[2], idx[205][205], tail[kMaxN], cur[kMaxN], dep[kMaxN];
bool ob[205][205], vis[kMaxN];

void adde(int u, int v, int w) { e[++tot] = {v, w, tail[u]}, tail[u] = tot; }
void add(int u, int v, int w) { adde(u, v, w), adde(v, u, 0); }

bool bfs() {
std::queue<int> q;
for (int i = 1; i <= t; ++i)
vis[i] = 0, dep[i] = kInf, cur[i] = tail[i];
q.emplace(s), dep[s] = 0, vis[s] = 1;
for (; !q.empty();) {
int u = q.front();
q.pop();
for (int i = tail[u]; i; i = e[i].pre) {
int v = e[i].v;
if (!e[i].w || vis[v]) continue;
vis[v] = 1, dep[v] = dep[u] + 1, q.emplace(v);
}
}
return vis[t];
}

int dfs(int u, int lim) {
if (u == t || !lim) return lim;
int flow = 0;
for (int &i = cur[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].w;
if (w && dep[v] == dep[u] + 1) {
int fl = dfs(v, std::min(lim, w));
if (!fl) dep[v] = kInf;
e[i].w -= fl, e[i ^ 1].w += fl;
lim -= fl, flow += fl;
if (!lim) break;
}
}
return flow;
}

void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int x, y;
std::cin >> x >> y;
ob[x][y] = 1;
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (ob[i][j]) continue;
idx[i][j] = ++cnt[(i + j) & 1];
}
}
s = cnt[0] + cnt[1] + 1, t = cnt[0] + cnt[1] + 2;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (ob[i][j]) continue;
if ((i + j) & 1) idx[i][j] += cnt[0];
}
}
for (int i = 1; i <= cnt[0]; ++i)
add(s, i, 1);
for (int i = 1; i <= cnt[1]; ++i)
add(i + cnt[0], t, 1);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (ob[i][j] || ((i + j) & 1)) continue;
for (auto [dx, dy] : kD) {
int ti = i + dx, tj = j + dy;
if (ti < 1 || ti > n || tj < 1 || tj > n || ob[ti][tj]) continue;
add(idx[i][j], idx[ti][tj], 1);
}
}
}
int ans = 0;
for (; bfs(); ans += dfs(s, kInf)) {}
std::cout << cnt[0] + cnt[1] - ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

考虑黑白染色,然后把相邻的点连一条流量为 ++\infty 的边,然后源点向黑点连流量为其权值的边,白点向汇点也连流量为其权值的边。

然后答案就是总和 - 最小割。

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
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e4 + 5, kMaxM = 1e5 + 5, kInf = 1e9;
const int kD[][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};

struct Edge {
int v, w, pre;
} e[kMaxM];

int n, m, s, t, sum, tot = 1;
int cnt[2], a[105][105], idx[105][105];
int tail[kMaxN], cur[kMaxN], dep[kMaxN];
bool vis[kMaxN];

int getid(int x, int y) {
return (x - 1) * m + y;
}

void adde(int u, int v, int w) { e[++tot] = {v, w, tail[u]}, tail[u] = tot; }
void add(int u, int v, int w) { adde(u, v, w), adde(v, u, 0); }

bool bfs() {
std::queue<int> q;
for (int i = 1; i <= t; ++i)
vis[i] = 0, dep[i] = kInf, cur[i] = tail[i];
q.emplace(s), dep[s] = 0, vis[s] = 1;
for (; !q.empty();) {
int u = q.front();
q.pop();
for (int i = tail[u]; i; i = e[i].pre) {
int v = e[i].v;
if (!e[i].w || vis[v]) continue;
vis[v] = 1, dep[v] = dep[u] + 1, q.emplace(v);
}
}
return vis[t];
}

int dfs(int u, int lim) {
if (u == t || !lim) return lim;
int flow = 0;
for (int &i = cur[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].w;
if (w && dep[v] == dep[u] + 1) {
int fl = dfs(v, std::min(lim, w));
if (!fl) dep[v] = kInf;
e[i].w -= fl, e[i ^ 1].w += fl;
lim -= fl, flow += fl;
if (!lim) break;
}
}
return flow;
}

void dickdreamer() {
std::cin >> n >> m;
s = n * m + 1, t = n * m + 2;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
std::cin >> a[i][j];
sum += a[i][j];
if ((i + j) & 1) add(s, getid(i, j), a[i][j]);
else add(getid(i, j), t, a[i][j]);
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (!((i + j) & 1)) continue;
for (auto [dx, dy] : kD) {
int ti = i + dx, tj = j + dy;
if (ti < 1 || ti > n || tj < 1 || tj > m) continue;
add(getid(i, j), getid(ti, tj), kInf);
}
}
}
int ans = 0;
for (; bfs(); ans += dfs(s, kInf)) {}
std::cout << sum - ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

第一问直接 DP。

对于第二问,设 fif_{i} 表示以 ii 开头的最长不下降子序列,那么如果 j>ij>i 并且 ajai,fj=fi1a_j\geq a_i,f_j=f_i-1 就连一条从 iijj 的边,容易发现形成了一张 DAG。

定义一个合法的链表示 f起点=mx,f终点=1f_{起点}=mx,f_{终点}=1,原题就转化为问能把 DAG 划分为最多多少条互不相交的链。

考虑把由于每个点最多出现 11 次,那么就把每个点拆成入点和出点,入点向出点连一条长度为 11 的边,如果有一条 iji\to j 的边,就连一条出点 ii 到入点 jj 的流量为 11 的边。

然后源点向 fi=mxf_i=mx 的入点 ii 连一条流量为 11 的边,表示可以从这里开始。fj=1f_j=1 的出点 jj 向汇点连一条流量为 11 的边,表示可以从这里结束。

跑最大流就是答案。

对于第三问直接把源点向入点 11、入点 11 向出点 11、入点 nn 向出点 nn 和出点 nn 向汇点的流量改成 ++\infty 再跑最大流即可。

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
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1005, kMaxM = 1e6 + 5, kInf = 1e9;

struct Edge {
int v, w, pre;
} e[kMaxM];

int n, s, t, tot = 1;
int a[kMaxN], f[kMaxN];
int tail[kMaxN], cur[kMaxN], dep[kMaxN];
bool vis[kMaxN];

void init() {
tot = 1;
memset(tail, 0, sizeof(tail));
}

void adde(int u, int v, int w) { e[++tot] = {v, w, tail[u]}, tail[u] = tot; }
void add(int u, int v, int w) { adde(u, v, w), adde(v, u, 0); }

bool bfs() {
std::queue<int> q;
for (int i = 1; i <= t; ++i)
vis[i] = 0, dep[i] = kInf, cur[i] = tail[i];
q.emplace(s), dep[s] = 0, vis[s] = 1;
for (; !q.empty();) {
int u = q.front();
q.pop();
for (int i = tail[u]; i; i = e[i].pre) {
int v = e[i].v;
if (!e[i].w || vis[v]) continue;
vis[v] = 1, dep[v] = dep[u] + 1, q.emplace(v);
}
}
return vis[t];
}

int dfs(int u, int lim) {
if (u == t || !lim) return lim;
int flow = 0;
for (int &i = cur[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].w;
if (w && dep[v] == dep[u] + 1) {
int fl = dfs(v, std::min(lim, w));
if (!fl) dep[v] = kInf;
e[i].w -= fl, e[i ^ 1].w += fl;
lim -= fl, flow += fl;
if (!lim) break;
}
}
return flow;
}

void dickdreamer() {
std::cin >> n;
s = 2 * n + 1, t = 2 * n + 2;
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
if (n == 1) { std::cout << "1\n1\n1\n"; return; }
int mx = 0;
for (int i = n; i; --i) {
f[i] = 1;
for (int j = i + 1; j <= n; ++j) {
if (a[j] >= a[i] && f[j] + 1 > f[i])
f[i] = f[j] + 1;
}
mx = std::max(mx, f[i]);
}
std::cout << mx << '\n';
for (int i = 1; i <= n; ++i) {
if (f[i] == mx) add(s, i, 1);
if (f[i] == 1) add(i + n, t, 1);
add(i, i + n, 1);
for (int j = i + 1; j <= n; ++j)
if (a[j] >= a[i] && f[j] + 1 == f[i])
add(i + n, j, 1);
}
int ans = 0;
for (; bfs(); ans += dfs(s, kInf)) {}
std::cout << ans << '\n';
init();
for (int i = 1; i <= n; ++i) {
if (f[i] == mx) add(s, i, (i == 1 ? kInf : 1));
if (f[i] == 1) add(i + n, t, (i == n ? kInf : 1));
add(i, i + n, (i == 1 || i == n) ? kInf : 1);
for (int j = i + 1; j <= n; ++j)
if (a[j] >= a[i] && f[j] + 1 == f[i])
add(i + n, j, 1);
}
ans = 0;
for (; bfs(); ans += dfs(s, kInf)) {}
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

考虑费用流,然后把每天拆成早上和晚上。

早上表示当天所有的干净餐巾,晚上表示当天所有的脏餐巾。

为了保证每天都有足够的餐巾,就要让早上 ii 向汇点连一条流量为 rir_i 的边,那么这样的话流量就少了 rir_i,考虑从源点向晚上 ii 连一条流量为 rir_i 且费用为 00 的边,补上丢失的流量,可以理解为顾客把 rir_i 个脏餐巾还给了饭店。

然后就让早上 ii 向早上 i+1i+1 连一条流量为 ++\infty 费用为 00 的边表示今天没用的餐巾可以留到明天用。晚上 ii 向晚上 i+1i+1 连一条流量 ++\infty 费用为 00 的边表示这些餐巾可以延迟送洗。

源点向早上 ii 连一条流量为 ++\infty 费用为 pp 的边,表示可以买新餐巾。晚上 ii 向早上 i+mi+m 连一条流量为 ++\infty 费用为 ff 的边,表示可以把脏餐巾送到快洗店。同理,晚上 ii 向早上 i+ni+n 连一条流量为 ++\infty 费用为 ss 的边,表示可以把脏餐巾送到慢洗店。

然后跑最小费用最大流即可。

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
#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 1e4 + 5, kMaxM = 1e5 + 5, kInf = 1e9;

struct Edge {
int v, flow, cost, pre;
} e[kMaxM];

int n, s, t, tot = 1, ans;
int _p, _m, _f, _n, _s, r[kMaxN];
int tail[kMaxN], cur[kMaxN], dis[kMaxN];
bool inq[kMaxN], vis[kMaxN];

int getid(int x, int op) { // 0 : 早, 1 : 晚
return x + op * n;
}

void adde(int u, int v, int f, int c) { e[++tot] = {v, f, c, tail[u]}, tail[u] = tot; }
void add(int u, int v, int f, int c) { adde(u, v, f, c), adde(v, u, 0, -c); }

bool spfa() {
for (int i = 1; i <= t; ++i) {
inq[i] = vis[i] = 0;
cur[i] = tail[i];
dis[i] = kInf;
}
std::queue<int> q;
q.emplace(s), dis[s] = 0, vis[s] = 1;
for (; !q.empty();) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = tail[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].cost, f = e[i].flow;
if (!f) continue;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v]) q.emplace(v), vis[v] = 1;
}
}
}
return dis[t] != kInf;
}

int dfs(int u, int lim) {
if (u == t || !lim) {
ans += dis[t] * lim;
return lim;
}
vis[u] = 1;
int flow = 0;
for (int &i = cur[u]; i; i = e[i].pre) {
int v = e[i].v;
if (dis[v] == dis[u] + e[i].cost && e[i].flow && !vis[v]) {
int fl = dfs(v, std::min(lim, e[i].flow));
e[i].flow -= fl, e[i ^ 1].flow += fl;
flow += fl, lim -= fl;
if (!lim) break;
}
}
vis[u] = 0;
return flow;
}

void dickdreamer() {
std::cin >> n;
s = 2 * n + 1, t = 2 * n + 2;
for (int i = 1; i <= n; ++i) {
std::cin >> r[i];
add(s, getid(i, 1), r[i], 0);
add(getid(i, 0), t, r[i], 0);
if (i < n) {
add(getid(i, 0), getid(i + 1, 0), kInf, 0);
add(getid(i, 1), getid(i + 1, 1), kInf, 0);
}
}
std::cin >> _p >> _m >> _f >> _n >> _s;
for (int i = 1; i <= n; ++i) {
add(s, getid(i, 0), kInf, _p);
if (i <= n - _m) add(getid(i, 1), getid(i + _m, 0), kInf, _f);
if (i <= n - _n) add(getid(i, 1), getid(i + _n, 0), kInf, _s);
}
for (; spfa(); dfs(s, kInf)) {}
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

原题可以转化为找两条点数最多的从 11nn 从小到大走的路径,且这两条路径除了 11nn,其余点不交。

套路拆点,然后最大流钦定找到 22 跳路径,最大费用用来找到尽可能多的点。

入点向出点连一条 (1,1)(1,1) 的边,特别的 11nn(2,1)(2,1)

然后如果 uuvv 有边,那么就出点 uu 和入点 vv 连一条 (1,0)(1, 0) 的边。

然后源点向入点 11 连一条 (2,0)(2, 0) 的边,出点 nn 向汇点也连 (2,0)(2,0) 的边,表示要找到两条合法路径。

然后跑最大费用最大流。

容易发现总点数是最大费用 - 2。

输出方案就在残余网络上找,如果 (u,v)(出u,入v) 满流了,就说明走了 (u,v)(u,v) 这条路径。

于是直接 dfs 两遍即可。

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
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 205, kMaxM = 1e5 + 5, kInf = 1e9;

struct Edge {
int v, flow, cost, pre;
} e[kMaxM];

int n, m, s, t, tot = 1;
int tail[kMaxN], cur[kMaxN], dis[kMaxN], ed[kMaxN][kMaxN];
std::string name[kMaxN];
bool vis[kMaxN];
std::map<std::string, int> idx;
std::vector<int> v[2];

void adde(int u, int v, int f, int c) { e[++tot] = {v, f, c, tail[u]}, tail[u] = tot, ed[u][v] = tot; }
void add(int u, int v, int f, int c) { adde(u, v, f, c), adde(v, u, 0, -c); }

bool spfa() {
for (int i = 1; i <= t; ++i) {
vis[i] = 0, dis[i] = -kInf, cur[i] = tail[i];
}
std::queue<int> q;
q.emplace(s), dis[s] = 0, vis[s] = 1;
for (; !q.empty();) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = tail[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].cost, f = e[i].flow;
if (!f) continue;
if (dis[v] < dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v]) q.emplace(v), vis[v] = 1;
}
}
}
return dis[t] != -kInf;
}

int dfs(int u, int lim) {
if (u == t || !lim) return lim;
vis[u] = 1;
int flow = 0;
for (int &i = cur[u]; i; i = e[i].pre) {
int v = e[i].v;
if (dis[v] == dis[u] + e[i].cost && e[i].flow && !vis[v]) {
int fl = dfs(v, std::min(lim, e[i].flow));
if (!fl) dis[v] = kInf;
e[i].flow -= fl, e[i ^ 1].flow += fl;
flow += fl, lim -= fl;
if (!lim) break;
}
}
vis[u] = 0;
return flow;
}

bool check(int u, int v) {
return e[ed[u][v + n] ^ 1].flow;
}

void _dfs(int u, int o) {
if (u == n) return;
v[o].emplace_back(u);
vis[u] = 1;
for (int i = u + 1; i <= n; ++i) {
if (!vis[i] && check(u, i)) {
return _dfs(i, o);
}
}
}

void dickdreamer() {
std::cin >> n >> m;
s = 2 * n + 1, t = 2 * n + 2;
for (int i = 1; i <= n; ++i) {
std::string str;
std::cin >> str;
name[i] = str;
idx[str] = i;
add(i + n, i, 1 + (i == n), 1);
}
for (int i = 1; i <= m; ++i) {
std::string s, t;
int u, v;
std::cin >> s >> t;
u = idx[s], v = idx[t];
if (u > v) std::swap(u, v);
add(u, v + n, kInf, 0);
}
add(s, 1, 2, 0), add(n, t, 2, 0);
int flow = 0;
for (; spfa(); flow += dfs(s, kInf)) {}
if (flow < 2) {
std::cout << "No Solution!\n";
return;
}
assert(flow == 2);
memset(vis, 0, sizeof(vis));
_dfs(1, 0), _dfs(1, 1);
std::reverse(v[1].begin(), v[1].end());
std::cout << v[0].size() + v[1].size() << '\n';
for (auto x : v[0]) std::cout << name[x] << '\n';
std::cout << name[n] << '\n';
for (auto x : v[1]) std::cout << name[x] << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

网络流
https://sobaliuziao.github.io/2023/09/16/post/89b04f08.html
作者
Egg_laying_master
发布于
2023年9月16日
许可协议