搜索题还是要多练,锻炼代码能力。
在写搜索之前一定要先想好状态如何表示,以及一些大概的程序框架。
0x21 树与图的遍历
164. 可达性统计
令 $f[x]$ 表示从 $x$ 出发可到达点的集合,那么 $f[x] = (\cup f[y]) \cup x$(存在边 $(x, y)$)。所以我们可以使用拓扑排序,倒着拓扑序进行计算。处理这个集合我们可以想到二进制压缩,但是一般二进制数没有这么大,所以需要 $bitset$。
bitset<N>
的空间大小是 $N/8$。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <bitset>
using namespace std;
const int N = 30010;
int n, m;
int h[N], e[N], ne[N], deg[N], idx;
int q[N];
bool vis[N];
bitset<N> d[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
deg[b] ++;
}
void topsort() {
queue<int> que;
for (int i = 1; i <= n; i ++)
if (deg[i] == 0) que.push(i);
int k = 0;
while (que.size()) {
int t = que.front(); que.pop();
q[++ k] = t;
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (-- deg[j] == 0) que.push(j);
}
}
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m --) {
int a, b; scanf("%d%d", &a, &b);
add(a, b);
}
topsort();
for (int i = n; i; i --) {
int j = q[i];
d[j][j] = 1;
for (int k = h[j]; ~k; k = ne[k])
d[j] |= d[e[k]];
}
for (int i = 1; i <= n; i ++) printf("%d\n", d[i].count());
return 0;
}
0x22 深搜
剪枝方法:
- 优化搜索顺序
- 排除等效冗余
- 可行性剪枝
- 最优性剪枝
- 记忆化
165. 小猫爬山
暴搜用脚写,考虑剪枝。
- 优化搜索顺序:我们很容易发现重量大的猫应该被放在搜索树的前面,所以我们可以给猫从大到小排序。
- 最优化剪枝:如果当前还没放完所有的猫重量就已经超过了目前的最小值,那么我们可以直接结束这一层递归。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 25;
int n, w, ans = 0x3f3f3f3f;
int c[N], d[N];
void dfs(int u, int cnt) {
if (u == n + 1) {
ans = min(ans, cnt);
return;
}
if (cnt >= ans) return;
d[cnt + 1] += c[u];
dfs(u + 1, cnt + 1);
d[cnt + 1] -= c[u];
for (int i = 1; i <= cnt; i ++) {
if (d[i] + c[u] > w) continue;
d[i] += c[u];
dfs(u + 1, cnt);
d[i] -= c[u];
}
}
int main() {
scanf("%d%d", &n, &w);
for (int i = 1; i <= n; i ++) scanf("%d", &c[i]);
sort(c + 1, c + 1 + n, [&](int a, int b) {
return a > b;
});
dfs(1, 0);
printf("%d\n", ans);
return 0;
}
166. 数独
暴搜一看就会超时,我们考虑剪枝。
剪枝:我们每次优先填能填的数最少的方格,这很符合人类的思维方式。到这里就可以结束了,后面还有一题需要玩过数独才会优化的题。
再加入一些常数优化,对于每一行、列、矩阵我们可以用一个二进制来表示能填的数的状态($1$ 表示该位能填,$0$ 表示不能)。一格 $(x,y)$ 能填数的状态就应该是 row[x] & col[y] & cell[x / 3][y / 3]
,然后我们再 $lowbit$ 枚举每一位 $1$ 即可。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 9;
int ones[1 << N], mp[1 << N];
int row[N], col[N], cell[3][3];
char s[110];
int lowbit(int x) {
return x & -x;
}
void init() {
for (int i = 0; i < N; i ++) row[i] = col[i] = (1 << N) - 1;
for (int i = 0; i < 3; i ++)
for (int j = 0; j < 3; j ++) cell[i][j] = (1 << N) - 1;
}
int get(int x, int y) {
return row[x] & col[y] & cell[x / 3][y / 3];
}
bool dfs(int cnt) {
if (cnt == 0) return true;
int minv = 10;
int x, y;
for (int i = 0; i < 9; i ++)
for (int j = 0; j < 9; j ++)
if (s[i * 9 + j] == '.') {
int t = ones[get(i, j)];
if (t < minv) {
minv = t;
x = i, y = j;
}
}
for (int i = get(x, y); i; i -= lowbit(i)) {
int t = mp[lowbit(i)];
row[x] -= 1 << t;
col[y] -= 1 << t;
cell[x / 3][y / 3] -= 1 << t;
s[x * 9 + y] = '1' + t;
if (dfs(cnt - 1)) return true;
row[x] += 1 << t;
col[y] += 1 << t;
cell[x / 3][y / 3] += 1 << t;
s[x * 9 + y] = '.';
}
return false;
}
int main() {
for (int i = 0; i < N; i ++) mp[1 << i] = i;
for (int i = 0; i < 1 << N; i ++)
for (int j = i; j; j -= lowbit(j)) ones[i] ++;
while (scanf("%s", s), *s != 'e') {
init();
int res = 81;
for (int i = 0; i < 9; i ++)
for (int j = 0; j < 9; j ++)
if (s[i * 9 + j] != '.') {
int t = s[i * 9 + j] - '1';
row[i] -= 1 << t;
col[j] -= 1 << t;
cell[i / 3][j / 3] -= 1 << t;
res --;
}
dfs(res);
printf("%s\n", s);
}
return 0;
}
0x23 剪枝
167. 木棒
我们先枚举最终木棒的长度 $l$,那么对于木棍总长 $s$,一定有 $s|l$,所以我们只需要枚举 $s$ 的因数即可。接下来是剪枝:
剪枝1:优化搜索顺序,将木棍长度从大到小排序。
剪枝2:由于我们这个问题是组合问题,所以我们要有一个搜索顺序。我们可以记录上一次搜的木棍下标 $i$,从 $i+1$ 开始搜索。
剪枝3:如果当前木棍 $i$ 失败了,那么换成任何木棍 $j$ 使得 $w_i = w_j$ 都肯定失败。
剪枝4:如果当前木棍 $i$ 为一根原始木棒(假定为 $x$)的开始,即第一根组成木棍,并且失败的话,我们就可以不继续往下搜了。反证法,如果可行的话,那么第 $i$ 根木棍可能会被替换到原始木棒 $y$ 中。那我们只要把第 $i$ 根木棍在 $y$ 中提前到开头,然后再调换 $x$ 和 $y$ 的顺序,我们就构造出了以 $i$ 为开头的原始木棒,就矛盾了。
剪枝5:如果当前木棍 $i$ 为一根原始木棒的结尾,那就一定失败。
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, w[N], l, s, maxv;
bool vis[N];
bool dfs(int cnt, int cur, int pos) {
if (cnt == s / l) return true;
if (cur == l) return dfs(cnt + 1, 0, 0);
for (int i = pos + 1; i <= n; i ++) {
if (vis[i]) continue;
if (cur + w[i] > l) continue;
vis[i] = true;
if (dfs(cnt, cur + w[i], i)) return true;
vis[i] = false;
if (!cur) return false;
if (cur + w[i] == l) return false;
int j = i + 1;
while (j <= n && w[i] == w[j]) j ++;
i = j - 1;
}
return false;
}
int main() {
while (scanf("%d", &n) == 1, n != 0) {
s = maxv = 0;
memset(vis, false, sizeof vis);
for (int i = 1; i <= n; i ++) {
scanf("%d", &w[i]);
s += w[i], maxv = max(maxv, w[i]);
}
sort(w + 1, w + 1 + n); reverse(w + 1, w + 1 + n);
for (l = maxv; l <= s; l ++)
if (s % l == 0 && dfs(0, 0, 0)) {
printf("%d\n", l);
break;
}
}
return 0;
}
168. 生日蛋糕
考虑朴素搜索,从第 $m$ 层开始搜索,每次记录一下当前搜到的层数 $dep$,当前体积 $v$,当前表面积 $s$,每一层枚举 $r$ 和 $h$ 但这样肯定超时。
剪枝1:由于 $v = r^2h, s = 2rh$,所以我们可以把 $r$ 和 $h$ 的搜索范围优化成 $r ∈ [dep, \min(\sqrt{n - v},r[dep + 1] - 1)], h ∈ [dep, \min(\frac{n - v}{r ^ 2}, h[dep + 1] - 1)]$。
剪枝2:我们可以预处理出 $minv[i]$ 和 $mins[i]$,分别表示 $1 \sim i$ 层最小的体积和表面积,具体的我们可以递推 $minv[i] = minv[i - 1] + i * i * i, mins[i] = mins[i - 1] + 2 * i * i$。如果当前 $v + minv[dep] > n$ 或者 $s + mins[dep] \geq ans$ 我们都不往下继续做。
剪枝3:剪枝1的范围我们从大到小搜索。
剪枝4:我们考虑 $1\sim i$ 层的体积可表示成 $n - v = \sum^{dep - 1}_{k = 1}h[k] \times r[k]^2$,表面积可表示成 $2\sum^{dep - 1}_{k = 1}h[k] \times r[k]$。我们考虑推式子,$2\sum^{dep - 1}_{k = 1}h[k] \times r[k] = \frac{2}{r[dep]}\sum^{dep - 1}_{k - 1}(h[k] * r[k] * r[dep]) \geq \frac{2}{r[dep]}\sum^{dep - 1}_{k - 1}(h[k] * r[k]^2) = \frac{2(n - v)}{r[dep]}$ ,所以当 $\frac{2(n - v)}{r[dep]} + s \geq ans$ 时,我们也不用继续往下做。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 30;
int n, m, ans = 0x3f3f3f3f;
int h[N], r[N], minv[N], mins[N];
void dfs(int dep, int v, int s) {
if (dep == 0) {
if (n == v) ans = min(ans, s);
return;
}
if (v + minv[dep] > n) return;
if (s + mins[dep] >= ans) return;
if (2 * (n - v) / r[dep + 1] + s >= ans) return;
for (int i = min((int)sqrt(n - v), r[dep + 1] - 1); i >= dep; i --)
for (int j = min((n - v) / i / i, h[dep + 1] - 1); j >= dep; j --) {
int t = 0;
if (dep == m) t = i * i;
r[dep] = i, h[dep] = j;
dfs(dep - 1, v + i * i * j, s + 2 * i * j + t);
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++) minv[i] = minv[i - 1] + i * i * i;
for (int i = 1; i <= m; i ++) mins[i] = mins[i - 1] + 2 * i * i;
r[m + 1] = h[m + 1] = 0x3f3f3f3f;
dfs(m, 0, 0);
printf("%d\n", ans == 0x3f3f3f3f ? 0 : ans);
return 0;
}
169. 数独2
大概就是上题数独的延伸版,现在我们考虑进一步剪枝。
首先对于所有只能填一个数的方格,我们直接填上。如果某个方格一个字母都不能填,说明该方案不合法。
接着,我们对于每个字母,如果它只能填在某一行、列、矩阵的某一个位置,我们直接填上。如果都不能填,该方案不合法。
做完以上步骤后,我们找一个可填字母数量最少的方格,枚举该方格填什么字母。
#include <bits/stdc++.h>
using namespace std;
const int N = 16, M = 1 << 16;
// b 开头的用来备份
char g[N][N + 1], bg[N * N + 5][N][N + 1];
int ones[M], lg[M];
int state[N][N], bstate[N * N + 5][N][N], bstate2[N * N + 5][N][N];
int lowbit(int x) {
return x & -x;
}
// 给 (x, y) 填上 num
void draw(int x, int y, int num) {
g[x][y] = 'A' + num;
for (int i = 0; i < N; i ++) state[x][i] &= ~(1 << num), state[i][y] &= ~(1 << num);
int tx = x / 4 * 4, ty = y / 4 * 4;
for (int i = 0; i < 4; i ++)
for (int j = 0; j < 4; j ++)
state[tx + i][ty + j] &= ~(1 << num);
state[x][y] = 1 << num;
}
// 拷贝不想写太多次,用函数压缩
void work(int kcnt) {
memcpy(state, bstate[kcnt], sizeof state);
memcpy(g, bg[kcnt], sizeof g);
}
bool dfs(int cnt) {
if (!cnt) return true;
int kcnt = cnt;
memcpy(bstate[kcnt], state, sizeof state);
memcpy(bg[kcnt], g, sizeof g);
// 对于所有只能填一个数的方格,我们直接填上。如果某个方格一个字母都不能填,说明该方案不合法。
for (int i = 0; i < N; i ++)
for (int j = 0; j < N; j ++) {
if (g[i][j] != '-') continue;
if (!state[i][j]) {
work(kcnt);
return false;
}
if (ones[state[i][j]] == 1) {
int t = lg[state[i][j]];
draw(i, j, t);
cnt --;
}
}
// 对于每个字母,如果它只能填在某一行的某一个位置,我们直接填上。如果都不能填,该方案不合法。
for (int i = 0; i < N; i ++) {
// sor 所有填字母方案的并集,sand 所有只能填一或零次的字母,drawn 已经填了的字母
int sor = 0, sand = M - 1, drawn = 0;
for (int j = 0; j < N; j ++) {
int t = state[i][j];
sand &= ~(sor & t);
sor |= t;
if (g[i][j] != '-') drawn |= t;
}
if (sor != M - 1) {
work(kcnt);
return false;
}
for (int j = sand; j; j -= lowbit(j)) {
int t = lowbit(j);
if (!(drawn & t)) {
for (int k = 0; k < N; k ++)
if (state[i][k] & t) {
draw(i, k, lg[t]);
cnt --;
break;
}
}
}
}
// 对于每个字母,如果它只能填在某一列的某一个位置,我们直接填上。如果都不能填,该方案不合法。
for (int i = 0; i < N; i ++) {
int sor = 0, sand = M - 1, drawn = 0;
for (int j = 0; j < N; j ++) {
int t = state[j][i];
sand &= ~(sor & t);
sor |= t;
if (g[j][i] != '-') drawn |= t;
}
if (sor != M - 1) {
work(kcnt);
return false;
}
for (int j = sand; j; j -= lowbit(j)) {
int t = lowbit(j);
if (!(drawn & t)) {
for (int k = 0; k < N; k ++)
if (state[k][i] & t) {
draw(k, i, lg[t]);
cnt --;
break;
}
}
}
}
//对于每个字母,如果它只能填在某一矩阵的某一个位置,我们直接填上。如果都不能填,该方案不合法。
for (int i = 0; i < N; i ++) {
int sor = 0, sand = M - 1, drawn = 0;
for (int j = 0; j < N; j ++) {
int x = i / 4 * 4, y = i % 4 * 4;
int dx = j / 4, dy = j % 4;
int t = state[x + dx][y + dy];
sand &= ~(sor & t);
sor |= t;
if (g[x + dx][y + dy] != '-') drawn |= t;
}
if (sor != M - 1) {
work(kcnt);
return false;
}
for (int j = sand; j; j -= lowbit(j)) {
int t = lowbit(j);
if (!(drawn & t)) {
for (int k = 0; k < N; k ++) {
int x = i / 4 * 4, y = i % 4 * 4;
int dx = k / 4, dy = k % 4;
if (state[x + dx][y + dy] & t) {
draw(x + dx, y + dy, lg[t]);
cnt --;
break;
}
}
}
}
}
// 找一个可填字母数量最少的方格,枚举该方格填什么字母
if (!cnt) return true;
int x, y, minv = 500;
for (int i = 0; i < N; i ++)
for (int j = 0; j < N; j ++)
if (g[i][j] == '-' && ones[state[i][j]] < minv) {
minv = ones[state[i][j]];
x = i, y = j;
}
memcpy(bstate2[kcnt], state, sizeof state);
for (int i = state[x][y]; i; i -= lowbit(i)) {
memcpy(state, bstate2[kcnt], sizeof state);
draw(x, y, lg[lowbit(i)]);
if (dfs(cnt - 1)) return true;
}
work(kcnt);
return false;
}
int main() {
for (int i = 0; i < M; i ++)
for (int j = i; j; j -= lowbit(j))
ones[i] ++;
for (int i = 0; i < N; i ++) lg[1 << i] = i;
while (cin >> g[0]) {
for (int i = 1; i < N; i ++) cin >> g[i];
for (int i = 0; i < N; i ++)
for (int j = 0; j < N; j ++)
state[i][j] = M - 1;
int cnt = 0;
for (int i = 0; i < N; i ++)
for (int j = 0; j < N; j ++)
if (g[i][j] != '-') {
draw(i, j, g[i][j] - 'A');
} else cnt ++;
dfs(cnt);
for (int i = 0; i < N; i ++, cout << endl)
for (int j = 0; j < N; j ++) cout << g[i][j];
cout << endl;
}
return 0;
}
真是酣畅淋漓。
0x24 迭代加深
170. 加成序列
迭代加深是一个非常简单的东西,有的题目的答案存在于搜索树比较浅的地方,所以我们可以通过限制搜索层数的方式更快地找出答案。
本题就是一个很好的例子,我们发现对于 $n \leq 100$ 的情况下,答案长度一定不会超过 $10$,因为我们可以通过诸如 $1,2,4,8..$ 的方式构造。所以我们选择迭代加深,并且在做的时候加入剪枝:从大到小枚举填的数,并且每次对填的数判重。
如何通过洛谷上 $n\leq10000$ 的数据?我们可以进一步剪枝。由我们的构造可知,我们最后一个数的最大值是 $2^{depth - 1}$。那么对于我们已经填了的数 $p[u]$,如果 $2^{depth - u} \times p[u] < n$ 说明我们一定没有答案,所以可以进行剪枝。
为什么是 $depth - 1$?因为我写的 $depth$ 是从 $1$ 开始的,构造的序列是从编号 $0$,所以要使用 $depth - 1$。因此我们是 $depth - u$ 而不是 $depth - u + 1$。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 10010;
int n, p[N];
bool dfs(int u, int depth) {
if (u == depth) return p[depth - 1] == n;
if ((LL)p[u - 1] * (((LL)1 << depth - u)) < n) return false;
bool st[N] = {false};
for (int i = u - 1; i >= 0; i --)
for (int j = u - 1; j >= i; j --) {
int s = p[i] + p[j];
if (!st[s] && s > p[u - 1] && s <= n) {
st[s] = true;
p[u] = s;
if (dfs(u + 1, depth)) return true;
}
}
return false;
}
int main() {
while (scanf("%d", &n), n) {
int depth = 1;
p[0] = 1;
while (!dfs(1, depth)) depth ++;
printf("1");
for (int i = 1; i < depth; i ++) printf(" %d", p[i]);
puts("");
}
return 0;
}
171. 送礼物
双向搜索即把一个数组劈成两半,先做前一半,再做后一半,然后再把两半通过某种方法合起来。
对于本题,直接搜索显然超时,所以我们可以把数组平分,处理出前一半数组所能构成的所有值 $w$,排序后去重。然后再计算出后一半所能构成的所有值 $s$,将 $s$ 中的每一个值 $s_i$ 对于 $w$ 跑二分,寻找最大的 $w_j$ 使得 $w_j + s_i \leq m$。、
我们再剪枝一下,在做之前把整个数组排个序,这样就完成了这道题。
注意计算过程中可能会暴 $int$。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 48;
int n, m, g[N], w[1 << (N / 2)], k;
int ans, cnt;
void dfs1(int u, int s) {
if (u == k) {
w[cnt ++] = s;
return;
}
if ((LL)g[u] + s <= m) dfs1(u + 1, s + g[u]);
dfs1(u + 1, s);
}
void dfs2(int u, int s) {
if (u == n) {
int l = 0, r = cnt - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if ((LL)w[mid] + s <= m) l = mid;
else r = mid - 1;
}
if ((LL)w[l] + s <= m) ans = max(ans, w[l] + s);
return;
}
if ((LL)g[u] + s <= m) dfs2(u + 1, s + g[u]);
dfs2(u + 1, s);
}
int main() {
scanf("%d%d", &m, &n);
for (int i = 0; i < n; i ++) scanf("%d", &g[i]);
sort(g, g + n);
reverse(g, g + n);
k = n >> 1;
dfs1(0, 0);
sort(w, w + cnt);
cnt = unique(w, w + cnt) - w;
dfs2(k, 0);
printf("%d\n", ans);
return 0;
}
0x25 广度优先搜索
172. 立体推箱子
我们记录状态为 $(x,y,k)$ 其中 $k = 0$ 表示该方块是立的,$k = 1$ 表示该方块是横的,$k = 2$ 表示该方块是竖的。$(x,y)$ 对于 $k = 0$ 时就存那一个格子的坐标,当 $k = 1$ 时,它存的是左边的那个方块的坐标,$k = 2$ 时存的就是上面的坐标。
对于每个方块,我们将其可能的坐标变换记录在 $d[3][4][3]$ 中。比如 $d[0][0] = (-2, 0, 2)$ 就意味着一个 $(0, x, y)$ 可以变成 $(2, x - 2, y)$。
然后做广搜就行了,在判断一个状态是否合法时,我们要对于 $k$ 分类讨论。
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int dist[N][N][3];
char g[N][N];
struct State {
int x, y, k;
} Start, ed;
void bfs() {
queue<State> q;
int d[3][4][3] = {
{{-2, 0, 2}, {0, 1, 1}, {1, 0, 2}, {0, -2, 1}},
{{-1, 0, 1}, {0, 2, 0}, {1, 0, 1}, {0, -1, 0}},
{{-1, 0, 0}, {0, 1, 2}, {2, 0, 0}, {0, -1, 2}}
};
q.push(Start);
memset(dist, -1, sizeof dist);
dist[Start.x][Start.y][Start.k] = 0;
while (q.size()) {
State t = q.front(); q.pop();
if (t.k == 0 && t.x == ed.x && t.y == ed.y) {
printf("%d\n", dist[t.x][t.y][0]);
return;
}
for (int j = 0; j < 4; j ++) {
int tx = t.x + d[t.k][j][0], ty = t.y + d[t.k][j][1], tk = d[t.k][j][2];
if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
if (g[tx][ty] == '#') continue;
if (tk == 0 && g[tx][ty] == 'E') continue;
if (tk == 1 && (ty + 1 > m || g[tx][ty + 1] == '#')) continue;
if (tk == 2 && (tx + 1 > n || g[tx + 1][ty] == '#')) continue;
if (dist[tx][ty][tk] != -1) continue;
dist[tx][ty][tk] = dist[t.x][t.y][t.k] + 1;
q.push({tx, ty, tk});
}
}
puts("Impossible");
}
int main() {
while (scanf("%d%d", &n, &m), n && m) {
for (int i = 1; i <= n; i ++) scanf("%s", g[i] + 1);
Start = {-1, -1, -1};
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
if (g[i][j] == 'X' && Start.x == -1) {
if (g[i][j + 1] == 'X') Start = {i, j, 1};
else if (g[i + 1][j] == 'X') Start = {i, j, 2};
else Start = {i, j, 0};
}
if (g[i][j] == 'O') ed = {i, j, 0};
}
bfs();
}
return 0;
}
173. 矩阵距离
把所有 $1$ 全扔进队列里跑广搜。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1010;
int n, m;
char g[N][N];
int d[N][N];
bool vis[N][N];
struct state {
int s, x, y;
};
void bfs() {
queue<state> q;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
if (g[i][j] == '1') {
q.push({0, i, j});
d[i][j] = 0;
}
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1};
while (q.size()) {
state t = q.front(); q.pop();
for (int i = 0; i < 4; i ++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 1 || x > n || y < 1 || y > m) continue;
if (d[x][y] != -1) continue;
d[x][y] = t.s + 1;
q.push({t.s + 1, x, y});
}
}
}
int main() {
memset(d, -1, sizeof d);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
scanf(" %c", &g[i][j]);
bfs();
for (int i = 1; i <= n; i ++, puts(""))
for (int j = 1; j <= m; j ++)
printf("%d ", d[i][j]);
return 0;
}
174. 推箱子
状态表示:$(x, y, dir)$ 其中 $dir$ 表示箱子是从哪个方向被推过来的。
然后我们可以在 $bfs$ 箱子的时候枚举箱子推向哪个方向,然后再 $bfs$ 人要到哪才能这样推箱子。
$bfs$ 时要记录路径,非常的麻烦。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 25;
struct state {
int x, y, dir;
} ed;
int n, m;
char g[N][N], ops[5] = "nswe";
PII d[N][N][4];
vector<int> path[N][N][4];
bool st[N][N][4], vis[N][N];
PII st_man, st_box;
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
state pre[N][N][4];
int pre_man[N][N];
void find() {
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
if (g[i][j] == 'S') st_man = {i, j};
if (g[i][j] == 'B') st_box = {i, j};
}
}
bool check(int x, int y) {
return x >= 1 && x <= n && y >= 1 && y <= m && g[x][y] != '#';
}
int bfs_man(PII st, PII ed, PII stone, vector<int> &p) {
memset(vis, false, sizeof vis);
memset(pre_man, -1, sizeof pre_man);
queue<PII> q;
q.push(st);
vis[st.first][st.second] = true;
vis[stone.first][stone.second] = true;
while (q.size()) {
PII t = q.front(); q.pop();
int x = t.first, y = t.second;
if (t == ed) {
p.clear();
while (~pre_man[x][y]) {
int dir = pre_man[x][y];
p.push_back(dir);
x = x + dx[dir], y = y + dy[dir];
}
return p.size();
}
for (int i = 0; i < 4; i ++) {
int tx = x - dx[i], ty = y - dy[i];
if (!check(tx, ty)) continue;
if (vis[tx][ty]) continue;
vis[tx][ty] = true;
pre_man[tx][ty] = i;
q.push({tx, ty});
}
}
return -1;
}
bool bfs_box() {
queue<state> q;
memset(st, false, sizeof st);
for (int i = 0; i < 4; i ++) {
int x = st_box.first + dx[i], y = st_box.second + dy[i]; // 人
int a = st_box.first - dx[i], b = st_box.second - dy[i]; // 盒子
if (!check(x, y) || !check(a, b)) continue;
vector<int> p;
int l = bfs_man(st_man, {x, y}, st_box, p);
if (l == -1) continue;
st[a][b][i] = true;
q.push({a, b, i});
pre[a][b][i] = {st_box.first, st_box.second, -1};
d[a][b][i] = {1, l};
path[a][b][i] = p;
}
PII maxv = {0x3f3f3f3f, 0x3f3f3f3f};
while (q.size()) {
state t = q.front(); q.pop();
PII &v = d[t.x][t.y][t.dir];
int a0 = t.x + dx[t.dir], b0 = t.y + dy[t.dir];
if (g[t.x][t.y] == 'T' && v < maxv) {
maxv = v;
ed = t;
}
for (int i = 0; i < 4; i ++) {
int x = t.x - dx[i], y = t.y - dy[i]; // box
int a = t.x + dx[i], b = t.y + dy[i]; // man
if (!check(x, y) || !check(a, b)) continue;
vector<int> p;
int l = bfs_man({a0, b0}, {a, b}, {t.x, t.y}, p);
if (l == -1) continue;
PII dist = {v.first + 1, v.second + l};
if (!st[x][y][i]) {
st[x][y][i] = true;
q.push({x, y, i});
pre[x][y][i] = t;
d[x][y][i] = dist;
path[x][y][i] = p;
} else if (d[x][y][i] > dist) {
pre[x][y][i] = t;
d[x][y][i] = dist;
path[x][y][i] = p;
}
}
}
return maxv.first != 0x3f3f3f3f;
}
int main() {
int Case = 0;
while (scanf("%d%d", &n, &m), n && m) {
printf("Maze #%d\n", ++ Case);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
scanf(" %c", &g[i][j]);
find();
if (!bfs_box()) {
puts("Impossible.");
} else {
string res;
while (~ed.dir) {
res += ops[ed.dir] - 32;
for (int i = 0; i < path[ed.x][ed.y][ed.dir].size(); i ++)
res += ops[path[ed.x][ed.y][ed.dir][i]];
ed = pre[ed.x][ed.y][ed.dir];
}
reverse(res.begin(), res.end());
cout << res << endl;
}
puts("");
}
return 0;
}
0x26 广搜变形
175. 电路维修
对于两个处于相对的格点,如果当前网格的连线与它们的连线重合则边权为 $0$,反之需要旋转一次,边权为 $1$。我们只要跑一段从左上角到右下角的最短路即可。
我们可以用双端队列来处理这个问题。因为 $bfs$ 时队列中的数据必须保证“两端性”和“单调性”,这样第一次访问到时就是答案。而在本题中我们可以用一个双端队列维护,即 $0$ 加入队头,$1$ 加入队尾。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 510;
int n, m, T;
char g[N][N];
int d[N][N];
bool vis[N][N];
int bfs() {
deque<PII> dq;
int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1};
int ix[4] = {-1, -1, 0, 0}, iy[4] = {-1, 0, 0, -1};
char ops[10] = "\\/\\/";
memset(d, 0x3f, sizeof d); d[0][0] = 0;
memset(vis, false, sizeof vis);
dq.push_front({0, 0});
while (dq.size()) {
PII t = dq.front(); dq.pop_front();
if (vis[t.first][t.second]) continue;
vis[t.first][t.second] = true;
for (int i = 0; i < 4; i ++) {
int x = t.first + dx[i], y = t.second + dy[i];
int a = t.first + ix[i], b = t.second + iy[i];
if (x >= 0 && x <= n && y >= 0 && y <= m) {
int w = 0; char c = ops[i];
if (g[a][b] != c) w = 1;
if (d[x][y] > d[t.first][t.second] + w) {
d[x][y] = d[t.first][t.second] + w;
if (w) dq.push_back({x, y});
else dq.push_front({x, y});
}
}
}
}
return d[n][m];
}
int main() {
scanf("%d", &T);
while (T --) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++) scanf(" %c", &g[i][j]);
int ans = bfs();
if (ans == 0x3f3f3f3f) puts("NO SOLUTION");
else printf("%d\n", ans);
}
return 0;
}
176. 装满的油箱
本题边权有很多种,所以我们可以使用优先队列来维护。这样子 $bfs$ 就变成了 $dijkstra$。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1010, M = 40010;
int n, m, T, p[N];
int h[N], e[M], w[M], ne[M], idx;
int d[N][N];
bool vis[N][N];
struct state {
int cost, city, rest;
bool operator < (const state &t) const {
return cost > t.cost;
}
};
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int bfs(int c, int s, int ed) {
memset(vis, false, sizeof vis);
memset(d, 0x3f, sizeof d);
priority_queue<state> heap;
heap.push({0, s, 0});
d[s][0] = 0;
while (heap.size()) {
state t = heap.top(); heap.pop();
if (t.city == ed) return t.cost;
if (vis[t.city][t.rest]) continue;
vis[t.city][t.rest] = true;
if (t.rest < c && d[t.city][t.rest + 1] > t.cost + p[t.city]) {
d[t.city][t.rest + 1] = t.cost + p[t.city];
heap.push({t.cost + p[t.city], t.city, t.rest + 1});
}
for (int i = h[t.city]; ~i; i = ne[i]) {
int j = e[i];
if (t.rest - w[i] >= 0 && d[j][t.rest - w[i]] > t.cost) {
d[j][t.rest - w[i]] = t.cost;
heap.push({t.cost, j, t.rest - w[i]});
}
}
}
return 0x3f3f3f3f;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++) scanf("%d", &p[i]);
while (m --) {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
add(a, b, c); add(b, a, c);
}
scanf("%d", &T);
while (T --) {
int c, s, e; scanf("%d%d%d", &c, &s, &e);
int ans = bfs(c, s, e);
if (ans == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", ans);
}
return 0;
}
177. 噩梦
双向 $BFS$,即男孩和女孩同时搜。每次男孩搜三步,女孩搜一步,在搜的时候记录一个格子是被女孩踩到的还是男孩踩到的,如果男孩和女孩都踩到了,说明两人相遇。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second
const int N = 810;
int n, m, T;
char g[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1};
int st[N][N];
PII boy, girl, ghost1, ghost2;
bool check(int x, int y, int gx, int gy, int k) {
return abs(gx - x) + abs(gy - y) > 2 * k;
}
int bfs() {
memset(st, 0, sizeof st);
queue<PII> qb, qg;
qb.push(boy); qg.push(girl);
st[boy.x][boy.y] = 1, st[girl.x][girl.y] = 2;
int step = 0;
while (qb.size() || qg.size()) {
step ++;
for (int i = 0; i < 3; i ++)
for (int j = 0, len = qb.size(); j < len; j ++) {
PII t = qb.front(); qb.pop();
if (!check(t.x, t.y, ghost1.x, ghost1.y, step) || !check(t.x, t.y, ghost2.x, ghost2.y, step)) continue;
for (int k = 0; k < 4; k ++) {
int a = t.x + dx[k], b = t.y + dy[k];
if (a < 0 || a >= n || b < 0 || b >= m || g[a][b] == 'X') continue;
if (!check(a, b, ghost1.x, ghost1.y, step) || !check(a, b, ghost2.x, ghost2.y, step)) continue;
if (!st[a][b]) {
st[a][b] = 1;
qb.push({a, b});
} else if (st[a][b] == 2) return step;
}
}
for (int j = 0, len = qg.size(); j < len; j ++) {
PII t = qg.front(); qg.pop();
if (!check(t.x, t.y, ghost1.x, ghost1.y, step) || !check(t.x, t.y, ghost2.x, ghost2.y, step)) continue;
for (int k = 0; k < 4; k ++) {
int a = t.x + dx[k], b = t.y + dy[k];
if (a < 0 || a >= n || b < 0 || b >= m || g[a][b] == 'X') continue;
if (!check(a, b, ghost1.x, ghost1.y, step) || !check(a, b, ghost2.x, ghost2.y, step)) continue;
if (!st[a][b]) {
st[a][b] = 2;
qg.push({a, b});
} else if (st[a][b] == 1) return step;
}
}
}
return -1;
}
int main() {
scanf("%d", &T);
while (T --) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++) scanf("%s", g[i]);
ghost1 = {-1, 0};
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++) {
if (g[i][j] == 'M') boy = {i, j};
else if (g[i][j] == 'G') girl = {i, j};
else if (g[i][j] == 'Z') {
if (ghost1.x == -1) ghost1 = {i, j};
else ghost2 = {i, j};
}
}
int ans = bfs();
printf("%d\n", ans);
}
return 0;
}
0x27 A*
$A^{*}$ 和 $IDA^{*}$ 算法最重要的就是估价函数。要注意估计的值一定要小于等于真实值,并且只能保证对于终点搜出正确的值,不能保证对其它点搜出正确的值。
178. 第K短路
因为 $k$ 短路一定大于等于最短路,所以我们可以将估价函数设计为从终点到这个点的最短路,然后跑 $A^{*}$。在我们搜索的时候,如果终点已经被访问了 $K$ 次,说明我们搜到了 $K$ 短路。如果某个点搜了超过 $K$ 次,那这个点可以不考虑,因为我们已经经过它 $K$ 次都没有找到答案,继续经过肯定找不到。
/*
证明终点第一次出队列即最优解
1 假设终点第一次出队列时不是最优
则说明当前队列中存在点u
有 d[估计]< d[真实]
d[u] + f[u] <= d[u] + g[u] = d[队头终点]
即队列中存在比d[终点]小的值,
2 但我们维护的是一个小根堆,没有比d[队头终点]小的d[u],矛盾
*/
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, PII> PIII;
const int N = 1010, M = 20010;
int n, m, S, T, K;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int f[N], dist[N], st[N];
bool vis[N];
void add(int h[], int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
void dijkstra() {
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, T});
memset(dist, 0x3f, sizeof dist);
dist[T] = 0;
while (heap.size()) {
auto t = heap.top(); heap.pop();
int ver = t.second;
if (vis[ver]) continue;
vis[ver] = true;
for (int i = rh[ver]; ~i; i = ne[i]) {
int j = e[i];
if (dist[ver] + w[i] < dist[j]) {
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
memcpy(f, dist, sizeof f);
}
int A_star(int S, int T, int K) {
priority_queue<PIII, vector<PIII>, greater<PIII>> heap;
heap.push({f[S], {0, S}});
while (heap.size()) {
auto t = heap.top(); heap.pop();
int distance = t.second.first, ver = t.second.second;
if (st[ver] >= K) continue;
st[ver] ++;
if (ver == T && st[ver] == K) return distance;
for (int i = h[ver]; ~i; i = ne[i]) {
int j = e[i];
if (st[j] < K) heap.push({distance + w[i] + f[j], {distance + w[i], j}});
}
}
return -1;
}
int main() {
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
scanf("%d%d", &n, &m);
while (m --) {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
add(h, a, b, c); add(rh, b, a, c);
}
scanf("%d%d%d", &S, &T, &K);
if (S == T) K ++;
dijkstra();
int ans = A_star(S, T, K);
printf("%d\n", ans);
return 0;
}
179. 八数码
估价函数:当前不在正确位置上的数的个数。
正确性显然,因为我们每次最多将空格和数交换一次,所以最多将一个数归位,估价函数一定小于真实值。
#include <bits/stdc++.h>
using namespace std;
string start, g, ed = "12345678x";
int f(string s) {
int res = 0;
for (int i = 0; i < s.size(); i ++) {
if (s[i] == 'x') continue;
int x = s[i] - '1';
res += abs(x / 3 - i / 3) + abs(x % 3 - i % 3);
}
return res;
}
string A_star() {
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
char ops[5] = "urdl";
unordered_map<string, int> dist;
unordered_map<string, bool> st;
unordered_map<string, pair<string, char>> path;
priority_queue<pair<int, string>, vector<pair<int, string>>, greater<pair<int, string>>> heap;
heap.push({f(g), g});
dist[g] = 0;
while (heap.size()) {
auto state = heap.top(); heap.pop();
string str = state.second;
if (str == ed) break;
if (st[str]) continue;
st[str] = true;
int x, y;
for (int i = 0; i < str.size(); i ++)
if (str[i] == 'x') x = i / 3, y = i % 3;
string t = str;
for (int i = 0; i < 4; i ++) {
int a = dx[i] + x, b = dy[i] + y;
if (a >= 0 && a < 3 && b >= 0 && b < 3) {
swap(t[a * 3 + b], t[x * 3 + y]);
if (!dist.count(t) || dist[t] > dist[str] + 1) {
dist[t] = dist[str] + 1;
path[t] = {str, ops[i]};
heap.push({f(t) + dist[t], t});
}
swap(t[a * 3 + b], t[x * 3 + y]);
}
}
}
string res;
while (ed != g) {
res += path[ed].second;
ed = path[ed].first;
}
reverse(res.begin(), res.end());
return res;
}
int main() {
char c;
while (cin >> c) {
g += c;
if (c != 'x') start += c;
}
int cnt = 0;
for (int i = 0; i < start.size(); i ++)
for (int j = i + 1; j < start.size(); j ++)
if (start[i] > start[j]) cnt ++;
if (cnt & 1) puts("unsolvable");
else cout << A_star() << endl;
return 0;
}
0x28 IDA*
180. 排书
我们可以发现每次操作至多改变 $3$ 本书的后继,所以估价函数可以设计为错误后继的个数除以三。
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int n, T, q[N], w[5][N];
int f() {
int res = 0;
for (int i = 1; i < n; i ++)
if (q[i + 1] != q[i] + 1) res ++;
return (res + 2) / 3;
}
bool check() {
for (int i = 1; i <= n; i ++)
if (q[i] != i) return false;
return true;
}
bool dfs(int dep, int maxdep) {
if (f() + dep > maxdep) return false;
if (check()) return true;
for (int len = 1; len <= n; len ++)
for (int l = 1; l + len - 1 <= n; l ++) {
int r = l + len - 1;
for (int k = r + 1; k <= n; k ++) {
memcpy(w[dep], q, sizeof q);
int u, v;
for (u = r + 1, v = l; u <= k; u ++, v ++) q[v] = w[dep][u];
for (u = l; u <= r; u ++, v ++) q[v] = w[dep][u];
if (dfs(dep + 1, maxdep)) return true;
memcpy(q, w[dep], sizeof q);
}
}
return false;
}
int main() {
scanf("%d", &T);
while (T --) {
scanf("%d", &n);
for (int i = 1; i <= n; i ++) scanf("%d", &q[i]);
int depth = 0;
while (depth < 5 && !dfs(0, depth)) depth ++;
if (depth == 5) puts("5 or more");
else printf("%d\n", depth);
}
return 0;
}
181. 回转游戏
估价函数:找出中间的八个格子中出现次数最多的数的出现次数 $k$,$8 - k$ 即为估价函数。因为每次至多该对一个位置。
/*
0 1
2 3
4 5 6 7 8 9 10
11 12
13 14 15 16 17 18 19
20 21
22 23
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int q[N], path[N];
char ops[9] = "ABCDEFGH";
int op[8][7] = {
{0, 2, 6, 11, 15, 20, 22},
{1, 3, 8, 12, 17, 21, 23},
{10, 9, 8, 7, 6, 5, 4},
{19, 18, 17, 16, 15, 14, 13},
{23, 21, 17, 12, 8, 3, 1},
{22, 20, 15, 11, 6, 2, 0},
{13, 14, 15, 16, 17, 18, 19},
{4, 5, 6, 7, 8, 9, 10}
};
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};
int opposite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int f() {
int sum[4] = {0};
for (int i = 0; i < 8; i ++) sum[q[center[i]]] ++;
int x = 0;
for (int i = 1; i <= 3; i ++) x = max(x, sum[i]);
return 8 - x;
}
bool check() {
int t = q[center[0]];
for (int i = 1; i < 8; i ++)
if (q[center[i]] != t) return false;
return true;
}
void work(int x) {
int t = q[op[x][0]];
for (int i = 0; i < 6; i ++)
q[op[x][i]] = q[op[x][i + 1]];
q[op[x][6]] = t;
}
bool dfs(int dep, int maxdep, int last) {
if (f() + dep > maxdep) return false;
if (check()) return true;
for (int i = 0; i < 8; i ++) {
if (last == opposite[i]) continue;
work(i);
path[dep] = i;
if (dfs(dep + 1, maxdep, i)) return true;
work(opposite[i]);
}
return false;
}
int main() {
while (cin >> q[0], q[0]) {
for (int i = 1; i < 24; i ++) cin >> q[i];
int dep = 0;
while (!dfs(0, dep, -1)) dep ++;
if (!dep) {
puts("No moves needed");
} else {
string ans;
for (int i = 0; i < dep; i ++) cout << char(path[i] + 'A');
cout << endl;
}
cout << q[6] << endl;
}
return 0;
}
182. 破坏正方形
对于当前状态,我们枚举所有还没被破坏的正方形,按序破坏它的所有边,但只记作一次操作。操作次数便是估价函数。
/*
0 1
2 3
4 5 6 7 8 9 10
11 12
13 14 15 16 17 18 19
20 21
22 23
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int q[N], path[N];
char ops[9] = "ABCDEFGH";
int op[8][7] = {
{0, 2, 6, 11, 15, 20, 22},
{1, 3, 8, 12, 17, 21, 23},
{10, 9, 8, 7, 6, 5, 4},
{19, 18, 17, 16, 15, 14, 13},
{23, 21, 17, 12, 8, 3, 1},
{22, 20, 15, 11, 6, 2, 0},
{13, 14, 15, 16, 17, 18, 19},
{4, 5, 6, 7, 8, 9, 10}
};
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};
int opposite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int f() {
int sum[4] = {0};
for (int i = 0; i < 8; i ++) sum[q[center[i]]] ++;
int x = 0;
for (int i = 1; i <= 3; i ++) x = max(x, sum[i]);
return 8 - x;
}
bool check() {
int t = q[center[0]];
for (int i = 1; i < 8; i ++)
if (q[center[i]] != t) return false;
return true;
}
void work(int x) {
int t = q[op[x][0]];
for (int i = 0; i < 6; i ++)
q[op[x][i]] = q[op[x][i + 1]];
q[op[x][6]] = t;
}
bool dfs(int dep, int maxdep, int last) {
if (f() + dep > maxdep) return false;
if (check()) return true;
for (int i = 0; i < 8; i ++) {
if (last == opposite[i]) continue;
work(i);
path[dep] = i;
if (dfs(dep + 1, maxdep, i)) return true;
work(opposite[i]);
}
return false;
}
int main() {
while (cin >> q[0], q[0]) {
for (int i = 1; i < 24; i ++) cin >> q[i];
int dep = 0;
while (!dfs(0, dep, -1)) dep ++;
if (!dep) {
puts("No moves needed");
} else {
string ans;
for (int i = 0; i < dep; i ++) cout << char(path[i] + 'A');
cout << endl;
}
cout << q[6] << endl;
}
return 0;
}
习题
183. 靶形数独
使用类似于例题中数独的方法,然后再写一个算分数的函数即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 9;
int g[N][N], res = -1;
int lg[1 << N], ones[1 << N];
int row[N], col[N], cell[3][3];
inline int lowbit(int x) {
return x & -x;
}
void init() {
for (int i = 0; i < 9; i ++) row[i] = col[i] = (1 << N) - 1;
for (int i = 0; i < 3; i ++)
for (int j = 0; j < 3; j ++)
cell[i][j] = (1 << N) - 1;
for (int i = 0; i < 9; i ++) lg[1 << i] = i;
for (int i = 0; i < 1 << 9; i ++)
for (int j = i; j; j -= lowbit(j)) ones[i] ++;
}
void draw(int x, int y, int c) {
int s = 1;
if (c > 0) g[x][y] = c;
else {
s = -1;
c = -c;
g[x][y] = 0;
}
c --;
row[x] -= (1 << c) * s;
col[y] -= (1 << c) * s;
cell[x / 3][y / 3] -= (1 << c) * s;
}
int score(int x, int y) {
return min(min(x, 8 - x), min(y, 8 - y)) + 6;
}
int get(int x, int y) {
return row[x] & col[y] & cell[x / 3][y / 3];
}
void dfs(int cnt, int sc) {
if (!cnt) {
res = max(res, sc);
return;
}
int x, y, minx = 10;
for (int i = 0; i < 9; i ++)
for (int j = 0; j < 9; j ++)
if (g[i][j] == 0 && ones[get(i, j)] < minx)
minx = ones[get(i, j)], x = i, y = j;
for (int i = get(x, y); i; i -= lowbit(i)) {
int k = lg[lowbit(i)] + 1;
draw(x, y, k);
dfs(cnt - 1, sc + score(x, y) * k);
draw(x, y, -k);
}
}
int main() {
for (int i = 0; i < N; i ++)
for (int j = 0; j < N; j ++)
scanf("%d", &g[i][j]);
init();
int cnt = 0, sc = 0;
for (int i = 0; i < N; i ++)
for (int j = 0; j < N; j ++)
if (g[i][j] == 0) cnt ++;
else {
sc += score(i, j) * g[i][j];
draw(i, j, g[i][j]);
}
dfs(cnt, sc);
printf("%d\n", res);
return 0;
}
184. 虫食算
我们考虑按照竖式中从个位到最高位的顺序将字母映射进一个 $q$ 数组中。
暴搜肯定超时,我们考虑剪枝:
- 如果最高位计算完后还有溢出的进位那么一定不合法。
- 对于一个数位,如果右边所有的数都填完了,那么直接判断是否合法
- 如果右边的数还没有填完,那么前一位的进位只有 $0$ 或 $1$ 两种可能,我们可以分别判断。
- 对于一个字母所代表的数,从大到小枚举。
#include <bits/stdc++.h>
using namespace std;
const int N = 100;
int n, q[N], h[N];
bool vis[N];
char s[N][N];
bool check() {
int t = 0;
for (int i = n - 1; i >= 0; i --) {
int A = h[s[0][i] - 'A'], B = h[s[1][i] - 'A'], C = h[s[2][i] - 'A'];
if (A != -1 && B != -1 && C != -1) {
if (t != -1) {
if ((A + B + t) % n != C) return false;
if (!i && A + B + t >= n) return false;
t = (A + B + t) / n;
} else {
if ((A + B) % n != C && (A + B + 1) % n != C) return false;
if (!i && A + B >= n) return false;
}
} else {
t = -1;
}
}
return true;
}
bool dfs(int u) {
if (u == n) return true;
for (int i = n - 1; i >= 0; i --)
if (!vis[i]) {
vis[i] = true;
h[q[u]] = i;
if (check() && dfs(u + 1)) return true;
h[q[u]] = -1;
vis[i] = false;
}
return false;
}
int main() {
scanf("%d", &n);
scanf("%s%s%s", s[0], s[1], s[2]);
int k = 0;
for (int i = n - 1; i >= 0; i --)
for (int j = 0; j < 3; j ++) {
int c = s[j][i] - 'A';
if (!vis[c]) {
q[k ++] = c;
vis[c] = true;
}
}
memset(vis, false, sizeof vis);
memset(h, -1, sizeof h);
dfs(0);
for (int i = 0; i < n; i ++) cout << h[i] << ' ';
return 0;
}
185. 玛雅游戏
神秘大模拟。如果某种数出现次数为 $1$ 或 $2$ 就一定无解。
#include <bits/stdc++.h>
using namespace std;
int n;
int g[5][7], bg[5][5][7];
int cnt[11], bcnt[5][11];
bool st[5][7];
struct Path {
int x, y, k;
} path[5];
void work(int x, int y, int c) {
swap(g[c][y], g[x][y]);
while (true) {
memset(st, false, sizeof st);
bool f = false;
for (int i = 0; i < 5; i ++) {
int z = 0;
for (int j = 0; j < 7; j ++)
if (g[i][j])
g[i][z ++] = g[i][j];
while (z < 7) g[i][z ++] = 0;
}
for (int i = 0; i < 5; i ++)
for (int j = 0; j < 7; j ++)
if (g[i][j]) {
int l = i, r = i;
while (l >= 0 && g[l][j] == g[i][j]) l --;
while (r < 5 && g[r][j] == g[i][j]) r ++;
if (r - l - 1 >= 3) {
st[i][j] = true;
f = true;
} else {
l = j, r = j;
while (l >= 0 && g[i][l] == g[i][j]) l --;
while (r < 7 && g[i][r] == g[i][j]) r ++;
if (r - l - 1 >= 3) {
st[i][j] = true;
f = true;
}
}
}
if (!f) break;
for (int i = 0; i < 5; i ++)
for (int j = 0; j < 7; j ++)
if (st[i][j]) {
cnt[g[i][j]] --;
g[i][j] = 0;
cnt[0] --;
}
}
}
bool dfs(int u) {
if (u == n) return !cnt[0];
for (int i = 1; i <= 10; i ++)
if (cnt[i] == 1 || cnt[i] == 2) return false;
memcpy(bg[u], g, sizeof g);
memcpy(bcnt[u], cnt, sizeof cnt);
for (int x = 0; x < 5; x ++)
for (int y = 0; y < 7; y ++)
if (g[x][y]) {
int nx = x + 1;
if (nx < 5) {
path[u] = {x, y, 1};
work(x, y, nx);
if (dfs(u + 1)) return true;
memcpy(g, bg[u], sizeof g);
memcpy(cnt, bcnt[u], sizeof cnt);
}
nx = x - 1;
if (nx >= 0) {
if (g[nx][y]) continue;
path[u] = {x, y, -1};
work(x, y, nx);
if (dfs(u + 1)) return true;
memcpy(g, bg[u], sizeof g);
memcpy(cnt, bcnt[u], sizeof cnt);
}
}
return false;
}
int main() {
scanf("%d", &n);
for (int i = 0; i < 5; i ++) {
int t, k = 0;
while (scanf("%d", &t), t) {
g[i][k ++] = t;
cnt[t] ++;
cnt[0] ++;
}
}
if (dfs(0)) for (int i = 0; i < n; i ++) printf("%d %d %d\n", path[i].x, path[i].y, path[i].k);
else puts("-1");
return 0;
}
186. 巴士
我们先预处理所有可能的等差数列,每个等差数列记录项数,首项,公差,然后按照项数从大到小排序。在搜索时我们迭代加深,记录当前还剩下多少步,当前已经处理了多少辆车,上一次枚举到了哪里。每次从上一次枚举到的地方开始(因为可能有重复的路线),如果当前枚举的项数乘上剩余步数加上已经处理好的车数小于总数就一定不可能成功(因为项数是排好序的)。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 310, M = 100;
int n, bus[M];
vector<pair<int, PII>> routes;
bool check(int a, int d) {
for (int i = a; i < 60; i += d)
if (!bus[i]) return false;
return true;
}
bool dfs(int dep, int last, int sum) {
if (!dep) return sum == n;
for (int i = last; i < routes.size(); i ++) {
int s = routes[i].first, a = routes[i].second.first, d = routes[i].second.second;
if (s * dep + sum < n) continue;
if (!check(a, d)) continue;
for (int j = a; j < 60; j += d) bus[j] --;
if (dfs(dep - 1, i, sum + s)) return true;
for (int j = a; j < 60; j += d) bus[j] ++;
}
return false;
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i ++) {
int t; scanf("%d", &t);
bus[t] ++;
}
for (int a = 0; a < 60; a ++)
for (int d = a + 1; d + a < 60; d ++)
if (check(a, d))
routes.push_back({(59 - a) / d + 1, {a, d}});
sort(routes.begin(), routes.end(), greater<pair<int, PII>>());
int dep = 0;
while (!dfs(dep, 0, 0)) dep ++;
printf("%d\n", dep);
return 0;
}
187. 导弹防御系统
IDDFS,在搜索时记录最大层数,当前搜到第几颗导弹,共有几个上升序列和下降序列。然后考虑一个贪心,如果我们要在一个上升序列中添加当前导弹,那么我们一定要把它添加在所有上升序列中最后一个导弹高度最高的中。比如当前有两个上升序列末尾分别为 $3$ 和 $4$,我们应该把 $5$ 加在 $4$ 后面。反之,如果要添加在下降序列中,我们应该选一个末尾最小的。
最后对于一个数,如果它无法添加在上升序列中,就另外开辟一个上升序列;如果无法添加到下降序列中,就另外开辟一个下降序列。
#include <bits/stdc++.h>
using namespace std;
const int N = 60;
int n, q[N];
int up[N], down[N];
bool dfs(int depth, int u, int su, int sd) {
if (su + sd > depth) return false;
if (u == n) return true;
bool f = false;
for (int i = 1; i <= su; i ++)
if (up[i] < q[u]) {
int t = up[i];
up[i] = q[u];
f = true;
if (dfs(depth, u + 1, su, sd)) return true;
up[i] = t;
break;
}
if (!f) {
up[su + 1] = q[u];
if (dfs(depth, u + 1, su + 1, sd)) return true;
}
f = false;
for (int i = 1; i <= sd; i ++)
if (down[i] > q[u]) {
int t = down[i];
down[i] = q[u];
f = true;
if (dfs(depth, u + 1, su, sd)) return true;
down[i] = t;
break;
}
if (!f) {
down[sd + 1] = q[u];
if (dfs(depth, u + 1, su, sd + 1)) return true;
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
while (cin >> n, n) {
for (int i = 0; i < n; i ++) cin >> q[i];
int dep = 0;
while (!dfs(dep, 0, 0, 0)) dep ++;
printf("%d\n", dep);
}
return 0;
}
188. 武士风度的牛
最简单的一集。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second
const int N = 210;
int n, m;
char g[N][N];
int d[N][N];
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
int bfs(int sx, int sy) {
queue<PII> q;
q.push({sx, sy});
d[sx][sy] = 0;
while (q.size()) {
PII t = q.front(); q.pop();
for (int i = 0; i < 8; i ++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 1 || x > n || y < 1 || y > m || g[x][y] == '*') continue;
if (g[x][y] == 'H') return d[t.x][t.y] + 1;
q.push({x, y});
g[x][y] = '*';
d[x][y] = d[t.x][t.y] + 1;
}
}
}
int main() {
scanf("%d%d", &m, &n);
int sx, sy;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
scanf(" %c", &g[i][j]);
if (g[i][j] == 'K') sx = i, sy = j;
}
printf("%d\n", bfs(sx, sy));
return 0;
}
189. 乳草的入侵
坑逼题目,所有给出的行列和坐标都是横竖颠倒的。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second
const int N = 110;
int n, m, sx, sy;
char g[N][N];
int d[N][N];
int bfs() {
memset(d, -1, sizeof d);
queue<PII> q;
q.push({sx, sy});
d[sx][sy] = 0;
int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1}, dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
while (q.size()) {
auto t = q.front(); q.pop();
for (int i = 0; i < 8; i ++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x >= 1 && x <= n && y >= 1 && y <= m && g[x][y] != '*') {
if (d[x][y] == -1) {
d[x][y] = d[t.x][t.y] + 1;
q.push({x, y});
}
}
}
}
int ans = 0;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
ans = max(ans, d[i][j]);
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> m >> n >> sy >> sx;
for (int i = n; i >= 1; i --)
for (int j = 1; j <= m; j ++)
cin >> g[i][j];
int ans = bfs();
cout << ans << endl;
return 0;
}
190. 字串变换
双向BFS,直接按照模板写即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int n;
string A, B;
string a[N], b[N];
int extend(queue<string> &q, unordered_map<string, int> &da, unordered_map<string, int> &db, string a[], string b[]) {
int d = da[q.front()];
while (q.size() && da[q.front()] == d) {
string s = q.front(); q.pop();
for (int i = 0; i < n; i ++)
for (int j = 0; j < s.size(); j ++)
if (s.substr(j, a[i].size()) == a[i]) {
string t = s.substr(0, j) + b[i] + s.substr(j + a[i].size());
if (db.count(t)) return da[s] + 1 + db[t];
if (da.count(t)) continue;
da[t] = da[s] + 1;
q.push(t);
}
}
return 15;
}
int bfs() {
queue<string> qa, qb;
unordered_map<string, int> da, db;
qa.push(A); qb.push(B);
da[A] = db[B] = 0;
int dep = 0;
while (qa.size() && qb.size()) {
int t;
if (qa.size() < qb.size()) t = extend(qa, da, db, a, b);
else t = extend(qb, db, da, b, a);
if (t <= 10) return t;
if (++ dep >= 10) return -1;
}
return -1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> A >> B;
if (A == B) {
cout << 0 << endl;
return 0;
}
while (cin >> a[n] >> b[n]) n ++;
int ans = bfs();
if (ans == -1) puts("NO ANSWER!");
else cout << ans << endl;
return 0;
}
191. 天气预报
容易发现,我们不用一个个格子判断是否 $7$ 天没下雨,我们只需要判断四个角落是否下雨即可。
我们考虑将搜索状态设计为:$(day, x, y, s_0, s_1, s_2, s_3)$,其中 $day$ 就是当前的天数,$(x, y)$ 表示当前 $2 \times 2$ 矩阵的左上角坐标,$s_0\sim s_3$ 分别表示每个角落连续没下雨的天数。然后广搜即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 370;
int n;
bool st[N][4][4][10][10][10][10];
int state[N][4][4];
int dx[9] = {-2, -1, 0, 0, 0, 0, 0, 1, 2}, dy[9] = {0, 0, -2, -1, 0, 1, 2, 0, 0};
struct node {
int d, x, y, s0, s1, s2, s3;
};
int bfs() {
queue<node> q;
if (state[1][1][1] || state[1][1][2] || state[1][2][1] || state[1][2][2]) return 0;
q.push({1, 1, 1, 1, 1, 1, 1});
st[1][1][1][1][1][1][1] = true;
while (q.size()) {
auto t = q.front(); q.pop();
int d = t.d;
if (d > n) return 1;
for (int i = 0; i < 9; i ++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x >= 0 && x + 1 < 4 && y >= 0 && y + 1 < 4 && !state[d + 1][x][y] && !state[d + 1][x][y + 1] && !state[d + 1][x + 1][y] && !state[d + 1][x + 1][y + 1]) {
int s0 = t.s0 + 1, s1 = t.s1 + 1, s2 = t.s2 + 1, s3 = t.s3 + 1;
if (x == 0 && y == 0) s0 = 0;
if (x == 0 && y == 2) s1 = 0;
if (x == 2 && y == 0) s2 = 0;
if (x == 2 && y == 2) s3 = 0;
if (s0 >= 7 || s1 >= 7 || s2 >= 7 || s3 >= 7) continue;
if (st[d + 1][x][y][s0][s1][s2][s3]) continue;
st[d + 1][x][y][s0][s1][s2][s3] = true;
q.push({d + 1, x, y, s0, s1, s2, s3});
}
}
}
return 0;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (cin >> n, n) {
memset(st, false, sizeof st);
for (int i = 1; i <= n; i ++)
for (int j = 0; j < 4; j ++)
for (int k = 0; k < 4; k ++)
cin >> state[i][j][k];
cout << bfs() << endl;
}
return 0;
}
192. 立体推箱子2
数据范围太大,考虑找规律。我们可以发现对于一个点 $(x, y)$ 使得 $x \% 3 = 0$ 且 $y \% 3 = 0$,那么它到 $0, 0$ 的距离就是 $x \div 3 \times2 + y\div3\times2$。所以我们可以 $bfs$ 找到对于起点最近的点 $(x, y)$,就做完了。
如何找呢?我们可以将起点映射到一张 $7 \times 7$ 的图的 $(sx \%3 + 3, sy\% 3 + 3)$,搜到第一个 $(x, y)$ 后再把坐标复原。
#include <bits/stdc++.h>
using namespace std;
const int N = 7;
typedef pair<int, int> PII;
int tx, ty;
struct state {
int x, y, dir; // 0: 立, 1:横, 2:竖
};
int d[3][4][3] = {
{{-2, 0, 2}, {0, 1, 1}, {1, 0, 2}, {0, -2, 1}},
{{-1, 0, 1}, {0, 2, 0}, {1, 0, 1}, {0, -1, 0}},
{{-1, 0, 0}, {0, 1, 2}, {2, 0, 0}, {0, -1, 2}}
};
int dist[N][N][3];
int bfs(int sx, int sy, int dir) {
memset(dist, -1, sizeof dist);
queue<state> q;
q.push({sx, sy, dir});
dist[sx][sy][dir] = 0;
int mind = 2e9;
while (q.size()) {
state t = q.front(); q.pop();
if (t.x % 3 == 0 && t.y % 3 == 0 && t.dir == 0) {
int nx = tx + t.x - 3, ny = ty + t.y - 3;
int xd = nx / 3 * 2, yd = ny / 3 * 2;
if (xd < 0 || yd < 0) continue;
mind = min(mind, xd + yd + dist[t.x][t.y][t.dir]);
}
for (int i = 0; i < 4; i ++) {
int x = t.x + d[t.dir][i][0], y = t.y + d[t.dir][i][1], k = d[t.dir][i][2];
if (x < 0 || x >= 7 || y < 0 || y >= 7) continue;
if (dist[x][y][k] == -1) {
dist[x][y][k] = dist[t.x][t.y][t.dir] + 1;
q.push({x, y, k});
}
}
}
return mind;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
char c;
while (cin >> c >> tx >> ty) {
int dir;
if (c == 'U') dir = 0;
else if (c == 'V') dir = 2;
else dir = 1;
int a = tx % 3 + 3, b = ty % 3 + 3;
int ans = bfs(a, b, dir);
cout << ans << endl;
}
return 0;
}
193. 算乘方的牛
剪枝:
- 负数和非初始状态的 $0$ 是没有意义的,所以我们在做同底数幂相除时一定是大的幂减去小的。
- 由于两个数相减或加它的最大公因数是不变的,所以如果当前两个数的最大公因数不是 $p$ 的因数就一定无解。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 50010;
int p;
int gcd(int x, int y) {
return y ? gcd(y, x % y) : x;
}
bool dfs(int dep, int num1, int num2) {
if (num1 > num2) swap(num1, num2);
if (num1 == p || num2 == p) return true;
if (!dep) return false;
if (gcd(num1, num2) && p % gcd(num1, num2)) return false;
if (num2 * (1 << dep) < p) return false;
if (dfs(dep - 1, num1 + num2, num2)) return true;
if (dfs(dep - 1, num1, num1 + num2)) return true;
if (dfs(dep - 1, num1 * 2, num2)) return true;
if (dfs(dep - 1, num1 * 2, num1)) return true;
if (dfs(dep - 1, num1, num2 * 2)) return true;
if (dfs(dep - 1, num2, num2 * 2)) return true;
if (dfs(dep - 1, num1, num2 - num1)) return true;
if (dfs(dep - 1, num2, num2 - num1)) return true;
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> p;
int dep = 0;
while (!dfs(dep, 0, 1)) dep ++;
cout << dep << endl;
return 0;
}
194. 涂满它!
由于我们每次操作最多只可能将一种相等的数加入联通块中,所以我们可以将估价函数设为不属于当前连通块的点的颜色种数类。然后我们在搜的时候维护 $st$ 数组,其中 $0$ 表示还没有拓展到,$1$ 表示在连通块中,$2$ 表示在连通块外部的边缘。我们每次只要对当前 $st$ 为 $2$ 的点跑 $floodfill$ 即可。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 10;
int n, g[N][N], dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1};
int st[N][N];
bool cnt[6];
bool vis[N][N];
int f() {
memset(cnt, false, sizeof cnt);
int s = 0;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
if (!cnt[g[i][j]] && st[i][j] != 1) {
cnt[g[i][j]] = true;
s ++;
}
return s;
}
void flood_fill(int x, int y, int c) {
st[x][y] = 1;
for (int i = 0; i < 4; i ++) {
int a = x + dx[i], b = y + dy[i];
if (a >= 1 && a <= n && b >= 1 && b <= n && st[a][b] != 1)
if (g[a][b] == c) flood_fill(a, b, c);
else st[a][b] = 2;
}
}
bool dfs(int depth) {
if (!f()) return true;
if (f() > depth) return false;
int bst[N][N]; memcpy(bst, st, sizeof st);
for (int c = 0; c < 6; c ++) {
bool is_color = false;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
if (st[i][j] == 2 && g[i][j] == c) {
flood_fill(i, j, c);
is_color = true;
}
if (is_color && dfs(depth - 1)) return true;
memcpy(st, bst, sizeof st);
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (cin >> n, n) {
memset(st, 0, sizeof st);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
cin >> g[i][j];
flood_fill(1, 1, g[1][1]);
int dep = 0;
while (!dfs(dep)) dep ++;
cout << dep << endl;
}
return 0;
}
195. 骑士精神
一步最多复原一个马,所以将估价函数设置为不在正确位置的马和空格数量。然后暴搜即可。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 6;
char g[N][N];
char ans[N][N] = {"11111", "01111", "00*11", "00001", "00000"};
int d[N][N];
int T; PII space;
int dx[8] = {-2, -2, -1, -1, 1, 1, 2, 2}, dy[8] = {-1, 1, -2, 2, -2, 2, -1, 1};
int f() {
int res = 0;
for (int i = 0; i < 5; i ++)
for (int j = 0; j < 5; j ++)
if (g[i][j] != ans[i][j]) res ++;
return res;
}
bool dfs(int depth) {
if (!f()) return true;
if (f() > depth) return false;
for (int i = 0; i < 8; i ++) {
int x = space.first + dx[i], y = space.second + dy[i];
if (x < 0 || x >= 5 || y < 0 || y >= 5) continue;
PII t = space;
swap(g[x][y], g[space.first][space.second]);
space = {x, y};
if (dfs(depth - 1)) return true;
space = t;
swap(g[x][y], g[space.first][space.second]);
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> T;
while (T --) {
for (int i = 0; i < 5; i ++) cin >> g[i];
for (int i = 0; i < 5; i ++)
for (int j = 0; j < 5; j ++)
if (g[i][j] == '*') space = {i, j};
int dep = 0;
while (dep <= 16 && !dfs(dep)) dep ++;
dep --;
if (dep > 15) dep = -1;
cout << dep << endl;
}
return 0;
}