2017 山东一轮集训 Day4」棋盘 题解

2017 山东一轮集训 Day4」棋盘 题解

混氏新子 蒟蒻

[[最小费用最大流]]

题目概述

题目链接

当时考试的时候没有想到网络流,就乱贪了一个,于是爆零了。后来发现这时一道很典的网络流题目,于是便想记录一下。

解题思路

很简单,还是横纵连二分图,把横着的连通块弄成一个点,竖着的连通块弄成一个点连边就行了。那源汇怎么记录费用呢?过一个点流量为时费用为时费用为,是呈平方级别增长的。在思考时遇到此处百思不得其解,遂向贺生求教,恍然大悟,原来你也玩原神 ,原来只用向一个点连几条流量最大为的边,费用依次为不得不服其妙哉。

于是很快写完代码,一遍过,再提交,乎,悲。

然后发现我每次询问都要 bfs 一次,这不是就炸裂了吗,和前面相同赋值跳过即可,无需再遍历。

然后也积累了些许经验,如稠密图不需要当前弧优化等等。

他们是 bfs 只增加一个增广路径,预处理,也感觉十分精妙,应该也能更加优化哉。

看代码

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
132
133
134
135
136
137
138
139
140
141
142
143
#include <bits/stdc++.h>
#define N 55
#define M N * N * N * N
#define inf (1 << 30)
using namespace std;
char s[N][N];
int n;
int q;
pair <int, int> cun[10010];
int ans[10010];
int lian[N][N][2];
int cnt;
int siz[N * N * 2];
int first[N * N], nxt[M << 1], to[M << 1], f[M << 1], c[M << 1], w[M << 1], et = 1;
int maxflow, minw, lim;
inline void add(int u, int v, int r, int co) {
nxt[++et] = first[u];
first[u] = et;
to[et] = v;
c[et] = r;
w[et] = co;
nxt[++et] = first[v];
first[v] = et;
to[et] = u;
w[et] = -co;
}
int dis[N * N];
bool exist[N * N];
queue<int> qq;
bool bfs() {
memset(dis, 0x3f, sizeof(dis));
dis[0] = 0;
qq.push(0);
while (!qq.empty()) {
int head = qq.front();
qq.pop();
exist[head] = 0;
for (int i = first[head]; i; i = nxt[i]) {
if (f[i] < c[i] && dis[head] + w[i] < dis[to[i]]) {
dis[to[i]] = dis[head] + w[i];
if (!exist[to[i]]) {
exist[to[i]] = 1;
qq.push(to[i]);
}
}
}
}
return dis[cnt] != 0x3f3f3f3f;
}
int dinic(int u, int flow) {
if (u == cnt) return flow;
int rest = flow;
exist[u] = 1;
for (int i = first[u]; i; i = nxt[i]) {
if (!exist[to[i]] && dis[to[i]] == dis[u] + w[i] && f[i] < c[i]) {
int k = dinic(to[i], min(rest, c[i] - f[i]));
if (!k) dis[to[i]] = inf;
else {
f[i] += k;
f[i ^ 1] -= k;
rest -= k;
minw += k * w[i];
}
}
}
exist[u] = 0;
return flow - rest;
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%s", s[i]);
}
for (int i = 0, j; i < n; ++i) { //记录点,加边
++cnt;
for (j = 0; j < n && s[i][j] == '#'; ++j);
for (; j < n; ++j) {
if (s[i][j] == '#') {
++cnt;
while (j + 1 < n && s[i][j + 1] == '#') ++j;
continue;
}
lian[i][j][0] = cnt;
}
}
for (int i = 0, j; i < n; ++i) {
++cnt;
for (j = 0; j < n && s[j][i] == '#'; ++j);
for (; j < n; ++j) {
if (s[j][i] == '#') {
++cnt;
while (j + 1 < n && s[j + 1][i] == '#') ++j;
continue;
}
lian[j][i][1] = cnt;
}
}
++cnt;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (s[i][j] == '#') continue;
add(lian[i][j][0], lian[i][j][1], 1, 0);
++siz[lian[i][j][0]];
--siz[lian[i][j][1]];
}
}
for (int i = 1; i < cnt; ++i) {
if (siz[i] > 0) {
for (int j = 0; j < siz[i]; ++j) {
add(0, i, 1, j);
}
} else {
for (int j = 0; j < -siz[i]; ++j) {
add(i, cnt, 1, j);
}
}
}
++cnt;
add(cnt - 1, cnt, 0, 0);
scanf("%d", &q);
for (int i = 0; i < q; ++i) {
scanf("%d", &cun[i].first);
cun[i].second = i;
}
sort(cun, cun + q); //从小到大更新,费用显然是单调递增的
for (int i = 0; i < q; ++i) {
//这里要写,不然容易超时,不写的话容易获得随机分数,从 40 到 100 不等
if (i && cun[i].first == cun[i - 1].first) {
ans[cun[i].second] = minw;
continue;
}
c[et ^ 1] = cun[i].first;
while (bfs()) {
int tmp;
while ((tmp = dinic(0, inf)) && maxflow != cun[i].first) maxflow += tmp;
}
ans[cun[i].second] = minw;
}
for (int i = 0; i < q; ++i) {
printf("%d\n", ans[i]);
}
return 0;
}

后记

码量不算大,模板打起来也快,网络流还需积累经验(多错几道)

  • 标题: 2017 山东一轮集训 Day4」棋盘 题解
  • 作者: 混氏新子
  • 创建于 : 2023-07-26 22:23:31
  • 更新于 : 2023-09-24 19:12:38
  • 链接: https://blog.huasushis.cn/2023/2017 山东一轮集训 Day4」棋盘 题解/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
此页目录
2017 山东一轮集训 Day4」棋盘 题解