前言

[kuangbin带你飞]是ACMer中名气最大的题单之一,起源是上海大学的kuangbin大佬,是从基础到进阶阶段的最好题单之一,虽然题量适中,题解繁多,不过大多冗杂,注释偏少。

本专栏致力于用尽量简短的描述,以及大量的代码注释,帮助同学们真正理解题目的含义与解题方法,详细地解析代码,从而帮助同学们提高代码能力(当然还是要自己动手),让同学们体验到算法的乐趣。

本专栏会持续更新迭代,欢迎各位算法爱好者提供意见与建议,从而督促本人做出更好的题解。能够迭代升级的,才是最强的题解。


相信大多数看这个题解的同学均是已经接触到kuangbin专题的了,但是为了让同学们学习路径更平滑,我还是想稍作补充:

包含kuangbin专题在内的,ACMer学习路径(必看):ACM 的正确入门方式是什么?

kuangbin专题合集,最全的合集:[kuangbin带你飞]专题1-23,本专题所有题目均在上面提交

kuangbin专题合集,ACwing评测(好处是不用忍受老OJ的老旧评测机,坏处是暂时没有更新完所有专题)


注意!kuangbin每个专题需要一定的知识基础,所以在学习之前希望各位同学有一定的学习途径。

以下是我常用的学习途径:

  1. (首推)ACwing算法基础课 + 算法提高课,性价比最高的网课,包含大量基础到进阶的知识点,强烈推荐入手,既可当算法百科查阅,也可系统学习打好基础。
  2. 《算法竞赛进阶指南》,非常契合kuangbin专题的一本书,既有很多知识点讲解,同时也提供了很多例题帮助理解,也是个很不错的题单,牛客 和 ACwing 上都可以刷题。
  3. OIWIKI,相当全的知识点介绍网站,可以通过这个网站初步了解知识点的原理与基本实现。

注意!本专栏的所有题解都会写出所有题目的主要知识点,方便同学们查找学习


A.棋盘问题

主要知识点:DFS,枚举

题意概括:

给定一个 n×nn\times nn×n 正方形的迷宫,其中每一行和每一列只能放一个棋子,而且只有特定的 “#” 号格子,才允许放一颗棋子

解题思路:

类似经典的“八皇后”问题,只不过加上了只有特定位置才能放棋子这一特点

最朴素的方式是用DFS枚举所有 “#” 号格子,如果同一行或同一列没有放过棋子(用一维数组记录这一行/列是否放过棋子进行标记),才会放下棋子

也可以像我这样,仅枚举每行的 ”#“ 号格子,节约了记录行的空间。

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 10 + 10;int n, k;
char g[N][N];       // 记录迷宫
int col[N];         // 对列上是否存在棋子进行标记
int ans;void dfs(int row, int cnt)  // row是当前行,cnt是已使用的棋子数
{if (cnt == k)    // 已经使用k颗棋子,该分支无需继续(剪枝){ans ++;return;}if (row == n) return;   // 所有行都枚举完了,而棋子数还未达条件for (int i = 0; i < n; i ++ ){// 枚举从所有列上的区域// 只有当前格子可以放棋子,且当前列上没有棋子,才能尝试放下这颗棋子if (g[row][i] == '#' && !col[i]){col[i] = 1; // 在当前列做标记// 放下这颗棋子,并枚举下一行dfs(row + 1, cnt + 1);   col[i] = 0;    // 这列不放棋子,回到原状态}}// 这一行不放棋子,继续枚举下一行dfs(row + 1, cnt);
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出while(cin >> n >> k && n != -1 ){ans = 0;memset(col, 0, sizeof col);// 行数从0 ~ n - 1for (int i = 0; i < n; i ++ ) cin >> g[i];// 初始值,第0行开始,当前一共使用了0颗棋子dfs(0, 0);cout << ans << endl;}// fclose(stdout);//输出结束return 0;
}

B.Dungeon Master(地牢大师)

主要知识点:BFS求最短路

题意概括:

给定一个 L×R×CL \times R \times CL×R×C 大小的3D地牢,其中 “S” 点为起点, “E” 点为终点,每单位时间能够向一个方格移动一格,包括上下层,无法进入 "#"号 格子。求能否到达终点,如果能到达,最小的步数是多少?

解题思路:

很明显的BFS问题,从起点开始,一步一步扩散,直到遍历到终点或者遍历完所有可到达的点而不能到达终点。

这道题除了代码量稍微大一点之外,就是一道正常的BFS题

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>using namespace std;const int N = 30 + 10;struct Position
{int floor, x, y;
};int L, R, C;
char g[N][N][N];
int dist[N][N][N];int bfs(Position st, Position ed) // st为起点,ed为终点
{// 前后左右方向的向量int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};// 用队列存储当前坐标queue<Position> q;q.push(st);int floor = st.floor, x = st.x, y = st.y;dist[floor][x][y] = 0;while (q.size()){Position t = q.front(); q.pop();floor = t.floor, x = t.x, y = t.y;if (g[floor][x][y] == 'E') return dist[floor][x][y]; for (int i = -1; i <= 1; i += 2){if (floor + i < 0 || floor + i >= L) continue;if (dist[floor + i][x][y] != -1) continue;if (g[floor + i][x][y] == '#') continue;dist[floor + i][x][y] = dist[floor][x][y] + 1;// 由于POJ的古老评测机,这里必须有一个中间变量才能编译Position k = {floor + i, x, y};q.push(k);}for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (a < 0 || a >= R || b < 0 || b >= C) continue;if (g[floor][a][b] == '#') continue;if (dist[floor][a][b] != -1) continue;dist[floor][a][b] = dist[floor][x][y] + 1;// 由于POJ的古老评测机,这里必须有一个中间变量才能编译Position k = {floor, a, b};q.push(k);}}return -1;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出while(cin >> L >> R >> C && L && R && C){memset(dist, -1, sizeof dist);for (int i = 0; i < L; i ++ )for (int j = 0; j < R; j ++ )cin >> g[i][j];Position st, ed;for (int i = 0; i < L; i ++ )for (int j = 0; j < R; j ++ )for (int k = 0; k < C; k ++ ){// 由于POJ的古老评测机,这里必须有一个中间变量才能编译Position t = {i, j, k};if (g[i][j][k] == 'S') st = t;if (g[i][j][k] == 'E') ed = t;}// 用一个变量存储函数返回值int distance = bfs(st, ed);// -1 表示到达不了终点,其他表示到达终点的时间if (distance == -1) puts("Trapped!");else printf("Escaped in %d minute(s).\n", distance);}// fclose(stdout);//输出结束return 0;
}

C.Catch That Cow(抓住那头牛)

主要知识点:BFS求最短路

题意概括:

牛和农夫都在一个数轴上,假设牛不动,而农夫有以下三种移动方式

(1)向正方向移动,X + 1

(2)向负方向移动,X - 1

(3)自身坐标翻倍,X * 2

求农夫需要移动多少次,才能到达牛的位置,抓住牛?

解题思路:

一维BFS问题,模拟,秒杀一气呵成。

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>using namespace std;const int N = 1e5 + 10;int n, k;
int dist[2 * N]; // 这里需要二倍长度,否则倍增的时候有可能会爆空间int bfs()
{queue<int> q;q.push(n);dist[n] = 0;while (q.size()){int t = q.front(); q.pop();if (t == k) return dist[k];// t + 1 不得超过 牛的极限坐标 if (t + 1 < N && dist[t + 1] == -1) {dist[t + 1] = dist[t] + 1;q.push(t + 1);}// t - 1 不得小于 0if (t - 1 >= 0 && dist[t - 1] == -1){dist[t - 1] = dist[t] + 1;q.push(t - 1);}// t * 2 不得超过 2倍 牛的极限坐标if (t * 2 < 2 * N && dist[2 * t] == -1){dist[2 * t] = dist[t] + 1;q.push(2 * t);}}return -1;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出cin >> n >> k;memset(dist, -1, sizeof dist);cout << bfs();// fclose(stdout);//输出结束return 0;
}

D.Fliptile(枚举)

主要知识点:状态压缩,枚举,递推,模拟

题意概括:

给定一个 M×NM \times NM×N 的0-1矩阵,要将这个矩阵转换成完全只有0的矩阵,每次翻转会连带着翻转相邻的四个格子(如果有的话),求最小翻转数方案中字典序最小的。

解题思路:

首先排除枚举所有节点的方案, 215×152 ^ {15 \times 15}215×15 必定超时,这是一道有点思维难度的题,我们必须具有递推思想,要想明白:在第一行的方案确定的情况下,为了保证上一行能够全为0,后面的所有行都可以由前一行的状态递推出来,也就是说,我们唯一需要枚举的,仅仅是第一行。

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>using namespace std;const int N = 15 + 10;int m, n;
// st为每个方格的状态, backup用于记录初始状态,方便回到初始状态
int st[N][N], backup[N][N];
// way代表当前操作的具体矩阵,last代表最终结果的操作矩阵
int way[N][N], last[N][N];
// ans记录最小操作个数
int ans = 0x3f3f3f3f;
// 翻转操作
void turn(int x, int y)
{way[x][y] = 1;    // 记录该操作// 一系列翻转操作if (x - 1 > 0) st[x - 1][y] = !st[x - 1][y];if (x + 1 <= m) st[x + 1][y] = !st[x + 1][y];st[x][y] = !st[x][y];if (y - 1 > 0) st[x][y - 1] = !st[x][y - 1];if (y + 1 <= n) st[x][y + 1] = !st[x][y + 1];
}
// 进行第一行的操作
int count (int state)
{int cnt = 0;  // cnt记录操作个数for (int i = 1; i <= n; i ++ ){if (state & (1 << i - 1)) {// 字典序从小到大的顺序,i应该从右往左进行操作turn(1, n - i + 1);cnt ++;}}return cnt;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出cin >> m >> n;// 输入初始状态for (int i = 1; i <= m; i ++ )for (int j = 1; j <= n; j ++ )cin >> st[i][j];// 将初始状态做备份memcpy(backup, st, sizeof st);// 枚举的是第一行的操作(状态压缩,字典序从小到大)for (int k = 0; k < 1 << n; k ++ ) {// 每次回到初始状态memcpy(st, backup, sizeof st);memset(way, 0, sizeof way);// count函数:进行第一行的操作int t = count(k);// 由第一行的状态,递推之后m - 1行的状态for (int i = 2; i <= m; i ++ ){for (int j = 1; j <= n; j ++ ){// 如果前一行的这一列的状态为1// 那么必须在当前一行的这一列进行一次翻转// 从而保证上一行全部为0if (st[i - 1][j])    {turn(i, j); t ++;}}}int ok = 1; // 用于标记是否成功将最后一行也变为0for (int i = 1; i <= n; i ++ )if (st[m][i])  // 只要最后一行有状态1存在ok = 0;     // 就标记为不成功if (ok)// 更新最小解,因为第一行是按字典序从小到大枚举的// 所以操作次数相同,第一次更新的就是最小字典序解if (t < ans)  {ans = t;memcpy(last, way, sizeof way);}}// 找不到可行解if (ans == 0x3f3f3f3f) puts("IMPOSSIBLE");else{// cout << ans << endl;for (int i = 1; i <= m; i ++ ){for (int j = 1; j <= n; j ++ )cout << last[i][j] << ' ';cout << endl;}}// fclose(stdout);//输出结束return 0;
}

E.Find The Multiple(找倍数)

主要知识点:BFS,完全二叉树,枚举

题意概括:

给定 1——2001——2001——200 的整数 nnn ,求 nnn 的倍数,这个倍数需要满足,在十进制表示中,仅由1和0两个数字组成,这个倍数在十进制上不超过100位。

解题思路:

这道题最基本的思路是BFS一位数一位数地拓展,直到找到能够整除的方案。但是 intintint 类型的数组不能存储足够大的数, longlonglong longlonglong 类型也是如此,只有 unsignedunsignedunsigned longlonglong longlonglong 才能勉强存下。

我们可以用同余进行优化,只存储每种方案的余数,这样我们就可以使用 intintint ,从而可以扩展更多方案。

这是一道数据很模糊的题,复杂度分析很困难,多项输入,但不知道项数,倍数的100位也完全是干扰项,最终测出的数据,最大为 219=5242882^{19} = 524288219=524288 ,但为了求稳,还是选择了将数组开到足够大 。

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>using namespace std;const int N = 1e8 + 10;int n;
int mod[N];     // mod[i]代表的是第i个数 MOD n的余数// 第i个数是指完全二叉树的第i个节点// 通过构建完全二叉树来枚举int bfs()
{queue<int> q;q.push(1);while(q.size()){int t = q.front(); q.pop();if (mod[t] == 0) return t;// 拓展左孩子if (2 * t < N){mod[2 * t] = mod[t] * 10 % n;q.push(2 * t);}// 拓展右孩子if (2 * t + 1 < N){mod[2 * t + 1] = (mod[t] * 10 + 1) % n;q.push(2 * t + 1);}}return 0;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出while (cin >> n && n){mod[1] = 1 % n;// 利用BFS,构建完全二叉树,每一层代表一位数字,最高位为1// 当枚举到mod == 0的情况停止int i = bfs(); // 提取mod == 0的那一个节点// 知道这个节点编号就可以求出他所有的祖先编号vector<int> ans;while(i){ans.push_back(i % 2);i /= 2;     // 访问父亲节点}reverse(ans.begin(), ans.end());for (i = 0; i < ans.size(); i ++ ) cout << ans[i];cout << endl;}// fclose(stdout);//输出结束return 0;
}

F.Prime Path(质数路径)

题意概括:

给定两个四位数的质数 aaa 和 bbb , aaa 为初状态, bbb 为末状态,要想从初状态变成末状态,可以进行以下操作:

选择当前质数,可以通过更换某一位的数字,来将这个质数变成另一个四位质数。

求最少需要多少次操作,才能将初状态变成末状态。

解题思路:

BFS问题,将每个四位质数作为一个状态,并通过条件判断能否转移,

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>using namespace std;const int N = 1e4 + 10;int a, b;
bool st[N];
int dist[N];    // 距离数组,记录需要几步到这个状态(质数)
int primes[N], p[N];
int cnt = 0, num = 0;// 求出所有的四位质数(质数筛)
void init()
{for (int i = 2; i < N; i ++ ){if (!st[i]){primes[cnt ++] = i;if (i >= 1000 && i <= 9999) p[num ++] = i;}for (int j = 0; primes[j] <= N / i; j ++ ){st[primes[j] * i] = 1;if (i % primes[j] == 0) break;}}
}
// 判断是否能够转移
bool ok(int x, int y)
{int ans = 0;while (x){int t1 = x % 10;int t2 = y % 10;x /= 10, y /= 10;if (t1 != t2) ans ++;}return ans == 1;
}void solve()
{// 记得初始化距离数组memset(dist, -1, sizeof dist);cin >> a >> b;queue<int> q;while(q.size()) q.pop();q.push(a);dist[a] = 0;while(q.size()){int t = q.front(); q.pop();if (t == b) {cout << dist[b] << endl;return;}// 枚举所有状态(四位质数)for (int j = 0; j < num; j ++ )    {// 判断能否转移 以及 是否到达过该状态if (ok(p[j], t) && dist[p[j]] == -1){dist[p[j]] = dist[t] + 1;q.push(p[j]);}}}if (dist[b] == -1) cout << "Impossible\n";
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出int t;cin >> t;init();while (t --){solve();}// fclose(stdout);//输出结束return 0;
}

G.Shuffle’m Up(洗牌)

主要知识点:BFS求最短路

题意概括:

给定两个字符串s1,s2,对其进行洗牌操作,即以下方式进行重组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9lmtCeRJ-1653023066918)(C:\Users\86133\AppData\Roaming\Typora\typora-user-images\image-20220520083415986.png)]

并将重组后的数组对半分,前半为s1,后半为s2,即可继续进行洗牌操作。

求最少需要多少次洗牌,才能将初状态变成末状态。

解题思路:

BFS问题,将s1+s2(合并)的长字符串作为状态,用洗牌操作更新状态,直到出现重复状态或目标状态

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>using namespace std;const int N = 1e5 + 10;int t;
int c;
string s1, s2, s;
map<string, bool> st; // 记录状态是否出现过int bfs()
{st.clear();string state = s1; string end = s;st[state] = 1;int ans = 0;while (state != end)   // 洗牌,直到出现目标状态{string cur = "";   for (int i = 0; i < c; i ++ ) {// 先加上s2的牌cur += state[i + c];// 再加上s1的牌cur += state[i];}if (st[cur]) return -1;      // 出现重复状态,不可能达到目标状态st[cur] = 1;ans ++;state = cur;}return ans;
}void solve()
{cin >> c;cin >> s1 >> s2 >> s;s1 += s2;  // 将s1,s2合并成为一个字符串作为状态cout << t << ' ' << bfs() << endl;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出int cnt;cin >> cnt;for (t = 1; t <= cnt; t ++ ){solve();}// fclose(stdout);//输出结束return 0;
}

H.Pots(水罐)

主要知识点:BFS求最短路

题意概括:

有两个水罐,可以进行以下三类操作(六种):

(1) 装满某个水罐(1号 或 2号)

(2) 清空某个水罐(1号 或 2号)

(3) 将某个水罐的水倒到另一个水罐(直到其中一个水罐满 或者 空)

状态设置是两个水罐中的水。

求最少需要多少次操作,才能将初状态变成末状态。

解题思路:

BFS问题,将两个水罐的水设为状态,枚举所有可能的操作,并记录前移状态以及其操作种类,通过末状态进行反向递推得到操作路径。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;typedef pair<int, int> State; // 用一对整数来代表状态const int N = 1e5 + 10;int a, b, c;struct Ways       // 操作方式
{int op, a, b;  // 操作种类,操作对象State st;        // 前一个状态
};
map<State, Ways> pre; // 记录一个状态的前一个,反向递推出路径
State ed;int bfs()
{State t;map<State, int> dist;    // 初状态到这个状态所需的步数for (int i = 0; i <= a; i ++ )for (int j = 0; j <= b; j ++ ){t = {i, j};dist[t] = -1;}queue<State> q;t = {0, 0};q.push(t);dist[t] = 0;pre[t] = {0, 0, 0, t};while(q.size()){State t = q.front(); q.pop();// 达到目标状态时,返回步数if (t.x == c || t.y == c) {ed = t;        // 记录最后状态,方便反推return dist[t];}State next;// 操作1,将第一个水罐装满if (t.x < a){next = {a, t.y};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {1, 1, 0, t};}}// 操作2,将第二个水罐装满if (t.y < b){next = {t.x, b};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {1, 2, 0, t};}}// 操作3,将第一个水罐清空if (t.x > 0){next = {0, t.y};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {2, 1, 0, t};}}// 操作4,将第二个水罐清空if (t.y > 0){next = {t.x, 0};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {2, 2, 0, t};}}// 操作5,将第一个水罐的水倒到第二个水罐if (t.x > 0 && t.y < b){int delta = min(b - t.y, t.x);next = {t.x - delta, t.y + delta};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {3, 1, 2, t};}}// 操作6,将第二个水罐的水倒到第一个水罐if (t.y > 0 && t.x < a){int delta = min(a - t.x, t.y);next = {t.x + delta, t.y - delta};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {3, 2, 1, t};}}}return -1;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出cin >> a >> b >> c;int n = bfs();     // n为达到目标状态的操作数if (n == -1) {puts("impossible");return 0;}cout << n << endl;vector<Ways> v;for (int i = 0; i < n; i ++ ){v.push_back(pre[ed]);  // 递推n个操作ed = pre[ed].st;      // 并得到前一个状态}// 反向输出,才是正确的顺序,因为我们是从末状态反向推出来的操作for (int i = n - 1; i >= 0; i -- ){Ways path = v[i];int op, x, y;op = path.op;x = path.a;y = path.b;if (op == 1){printf("FILL(%d)\n", x);}else if (op == 2){printf("DROP(%d)\n", x);}else printf("POUR(%d,%d)\n", x, y);}// fclose(stdout);//输出结束return 0;
}

I.Fire Game(玩火)(本题提交与ACwing评测机)

主要知识点:BFS求最短路

题意概括:

两个小孩在玩火,只有草丛 “#” 才能烧起来,两个小孩会在同时点燃两个草丛(也可能是同一个),求把地图上所有草丛都烧着的最短时间。

解题思路:

BFS问题,首先需要判断连通块个数,如果连通块大于2,则两个小孩不能通过一次点火点燃所有草丛;如果连通块等于2,则两个小孩需要分开点燃两堆草丛;如果连通块等于1,则两个小孩可以在这一堆里面任选 1~2 个草丛点燃。

点燃之后,计算所有草丛被点燃时的时间dist,最大的时间就是所有草丛都烧着的时间,枚举所有方案,并记录其中的最小值。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;typedef pair<int, int> PII;const int N = 100 + 10;int n, m;
char g[N][N];
int dist[N][N];     // 当前草丛烧起来的所花的最短时间(顺带记录是否烧起来过)
vector<PII> p[N]; // 记录连通块中的节点
int cnt;            // 连通块(草丛)的个数cntint bfs(int flag, int x1, int y1, int x2, int y2)
{int mx = 0;// 判断连通块时千万不能每次都更新dist(记录是否访问)!if (flag > 1) memset(dist, -1, sizeof dist);int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};queue<PII> q;q.push({x1, y1});   dist[x1][y1] = 0;if (flag == 1 || (x1 == x2 && y1 == y2));else {q.push({x2, y2});    // 有时需要两个火源进行BFSdist[x2][y2] = 0;}while (q.size()){PII t = q.front(); q.pop();int x = t.x, y = t.y;if (flag == 1) {p[cnt].push_back(t);}mx = max(mx, dist[x][y]);for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];PII next = {a, b};if (a <= 0 || a > n || b <= 0 || b > m) continue;if (g[a][b] == '#' && dist[a][b] == -1){q.push(next);dist[a][b] = dist[x][y] + 1;}}}// cout << mx << endl;return mx;
}
// 判断连通块
void check()
{cnt = 0;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ )if (g[i][j] == '#' && dist[i][j] == -1) {cnt ++;if (cnt > 2) return;bfs(1, i, j, 0, 0);      // 对当前连通块进行标记}
}
// 枚举两个连通块的情况
int both()
{int mn = 1e9;// 两个小孩必须分别在两个连通块中放火for (int i = 0; i < p[1].size(); i ++ )for (int j = 0; j < p[2].size(); j ++ ){PII t1 = p[1][i], t2 = p[2][j];mn = min(mn, bfs(2, t1.x, t1.y, t2.x, t2.y));}return mn;
}
// 枚举一个连通块的情况
int only()
{int mn = 1e9;// 两个小孩在同一个连通块中放火,可以是同个点for (int i = 0; i < p[1].size(); i ++ )for (int j = 0; j < p[1].size(); j ++ ){PII t1 = p[1][i], t2 = p[1][j];mn = min(mn, bfs(3, t1.x, t1.y, t2.x, t2.y));}return mn;
}int solve()
{memset(dist, -1, sizeof dist);p[1].clear();p[2].clear();cin >> n >> m;for (int i = 1; i <= n; i ++) cin >> g[i] + 1;check(); // 判断连通块(草丛)的个数cntint ans;if (cnt > 2) ans = -1;else if (cnt == 2) ans = both();else if (cnt) ans = only();else ans = -1;return ans;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出int t;cin >> t;for (int i = 1; i <= t; i ++ ){int ans = solve();printf("Case %d: %d\n", i, ans);}// fclose(stdout);//输出结束return 0;
}

I.Fire!(火灾)

主要知识点:BFS求最短路

题意概括:

在一个迷宫里,有复数个火源 和 一位逃生者,逃生者和火源都会以1格每单位时间的速度移动,逃生者需要从迷宫边缘逃生,求最短时间。

解题思路:

BFS问题,先对所有火源进行多源BFS,求出每个点被火焰燃烧的时间点,然后让逃生者进行模拟逃生(BFS),每个点,只有在火焰燃烧到之前到达,才能更新到达时间,否则就无法到达。

当BFS拓展到任何一个边界时,下一步就可以直接走出迷宫。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;typedef pair<int, int> PII;const int N = 1000 + 10;int n, m;
char g[N][N];
// dist是火焰到达的时间点, d是逃生者到达的时间点
int dist[N][N], d[N][N];
int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
// 火源先拓展
void bfs1()
{// 这里必须用一个较大的值来表示这个点火焰没有到达memset(dist, 0x3f, sizeof dist);queue<PII> q1;while (q1.size()) q1.pop();// 导入所有火源for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ )if (g[i][j] == 'F'){PII t = {i, j};q1.push(t);dist[i][j] = 0;}// 正常的BFS拓展while (q1.size())    {PII t = q1.front(); q1.pop();int x = t.x, y= t.y;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (g[a][b] == '#') continue;if (a <= 0 || a > n || b <= 0 || b > m) continue;if (dist[a][b] == 0x3f3f3f3f){dist[a][b] = dist[x][y] + 1;PII next = {a, b};q1.push(next);} }}
}
// 逃生者模拟逃生
int bfs2()
{// 这里就不需要用大数标记,用-1表示是否到达过就行了。memset(d, -1, sizeof d);queue<PII> q;while (q.size()) q.pop();for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ )if (g[i][j] == 'J'){PII t = {i, j};q.push(t);d[i][j] = 0;}   while (q.size()){PII t = q.front(); q.pop();int x = t.x, y = t.y;// 第一次到达边界,答案就是加一步出迷宫if (x == 1 || x == n || y == 1 || y == m) return d[x][y] + 1;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (g[a][b] == '#') continue;if (a <= 0 || a > n || b <= 0 || b > m) continue;// 只有dist大于d,才能进行拓展(这就是为什么要用大数标记dist)if (d[x][y] + 1 < dist[a][b] && d[a][b] == -1){PII next = {a, b};q.push(next);d[a][b] = d[x][y] + 1;}}   }    return -1;
}void solve()
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1; bfs1();int ans = bfs2();if (ans == -1) puts("IMPOSSIBLE");else cout << ans << endl;// for (int i = 1; i <= n; i ++ )  // {//     for (int j = 1; j <= n; j ++ )//         cout << dist[i][j] << ' ';//     cout << endl;// }
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出int t;cin >> t;for (int i = 1; i <= t; i ++ ){solve();}// fclose(stdout);//输出结束return 0;
}

J.迷宫问题

主要知识点:BFS求最短路

题意概括:

给定一个 $5 \times 5 $ 的迷宫矩阵,求左上角 (0,0)(0,0)(0,0) 到 右下角 (4,4)(4,4)(4,4) 的最短路径

注意,输出所有走过的点,即为路径

解题思路:

BFS问题,只不过需要记录路径。

在水罐那道题我们已经见识了反推路径,这道题我们来用个新的方法,从终点开始走到起点,这样直接得到的就是正向路径了。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;typedef pair<int, int> PII;const int N = 10 + 10;int g[N][N];
int st[N][N];       // 用一个数组记录是否重复走过就行了
map<PII, PII> pre;    // 记录前一个节点int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};void bfs(PII ed)
{memset(st, 0, sizeof st);queue<PII> q;q.push(ed);st[ed.x][ed.y] = 1;while(q.size()){PII t = q.front(); q.pop();int x = t.x, y = t.y;if (x == 0 && y == 0) return;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];PII next;next = {a, b};if (a < 0 || b < 0 || a >= 5 || b >= 5) continue;if (g[a][b] == 1) continue;if (st[a][b] == 1) continue;q.push(next);pre[next] = t;st[a][b] = 1;}}
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出for (int i = 0; i < 5; i ++ )for (int j = 0; j < 5; j ++ )cin >> g[i][j];PII ed;ed = {4, 4};bfs(ed);PII state;state = {0, 0};while(state.x != 4 || state.y != 4){// 得到节点,直接输出printf("(%d, %d)\n", state.x, state.y);state = pre[state];}// 最后一个节点手动输出printf("(%d, %d)\n", state.x, state.y);// fclose(stdout);//输出结束return 0;
}

K.Oil Deposits(石油储量)

主要知识点:BFS求最短路

题意概括:

可以认为,在以某个石油的九宫格内的石油,都是同一批石油,否则则认为不是同一批;给定一个地图,求地图上有多少批不同的石油。

解题思路:

BFS判断连通块问题,判断有多少个连通块即可,连通块的注释可以看下"玩火"那道题

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;typedef pair<int, int> PII;const int N = 100 + 10;int n, m;
char g[N][N];
int st[N][N];int cnt;
void bfs(int i, int j)
{queue<PII> q;q.push({i, j});st[i][j] = cnt;while (q.size()){PII t = q.front(); q.pop();int x = t.x, y = t.y;for (int i = -1; i <= 1; i ++ )for (int j = -1; j <= 1; j ++ ){int a = x + i, b = y + j;if (a <= 0 || a > n || b <= 0 || b > m) continue;if (st[a][b]) continue;if (g[a][b] != '@') continue;PII next = {a, b};q.push(next);st[a][b] = 1;}}
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出while (cin >> n >> m && n){memset(st, 0, sizeof st);cnt = 0;for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){if (g[i][j] == '@' && st[i][j] == 0) {cnt ++;bfs(i, j);}}cout << cnt << endl;}// fclose(stdout);//输出结束return 0;
}

L.非常可乐

主要知识点:BFS求最短路

题意概括:

一瓶可乐的容量为s,有两个空杯子容量分别为n,m;

三个杯/瓶可以相互倒,每次倒可乐算一步,求恰好平分可乐的最小步数。

解题思路:

类似于水罐那道题,只不过这次我们需要自己分析操作的种类。

将三个杯/瓶分别称为 a, b, c。

(1)a -> b

(2)a -> c

(3)b -> a

(4)b -> c

(5)c -> a

(6)c -> b

倒水的方式跟水罐那道题相互倒水几乎一样。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;typedef pair<int, int> PII;
typedef pair<int, pair<int, int>> PIII;
const int N = 100 + 10;int s, n, m;int dist[N][N][N]; // 记录到某个状态的最短步数int bfs()
{memset(dist, -1, sizeof dist);queue<PIII> q;PIII start = {s, {0, 0}};   // 这里可以用结构体定义,可读性会强点q.push(start);dist[s][0][0] = 0;while (q.size()){PIII t = q.front(); q.pop();int a = t.x, b = t.y.x, c = t.y.y;// 终止状态// 如果其中一杯已经满足有一半的可乐了,那么另一半一定在另外两杯中if (a == s / 2 || c == s / 2 || b == s / 2) {int ans = dist[a][b][c];// 如果没有一个为空(则另外一个也是一半)// 则需要再多加一步将两个合并为另一半可乐if (a && b && c) ans ++;return ans;}if (a < s){// (1)a -> bif (b > 0){int da = min(s - a, b);PIII next = {a + da, {b - da, c}};if (dist[a + da][b - da][c] == -1) {dist[a + da][b - da][c] = dist[a][b][c] + 1;q.push(next);}}// (2)a -> cif (c > 0){int da = min(s - a, c);PIII next = {a + da, {b, c - da}};if (dist[a + da][b][c - da] == -1){dist[a + da][b][c - da] = dist[a][b][c] + 1;q.push(next);}}}if (b < n){// (3)b -> aif (a > 0){int db = min(n - b, a);PIII next = {a - db, {b + db, c}};if (dist[a - db][b + db][c] == -1){dist[a - db][b + db][c] = dist[a][b][c] + 1;q.push(next);}}// (4)b -> cif (c > 0){int db = min(n - b, c);PIII next = {a, {b + db, c - db}};if (dist[a][b + db][c - db] == -1){dist[a][b + db][c - db] = dist[a][b][c] + 1;q.push(next);}}}if (c < m){// (5)c -> aif (a > 0){int dc = min(m - c, a);PIII next = {a - dc, {b, c + dc}};if (dist[a - dc][b][c + dc] == -1) {dist[a - dc][b][c + dc] = dist[a][b][c] + 1;q.push(next);}}// (6)c -> bif (b > 0){int dc = min(m - c, b);PIII next = {a, {b - dc, c + dc}};if (dist[a][b - dc][c + dc] == -1) {dist[a][b - dc][c + dc] = dist[a][b][c] + 1;q.push(next);}}}}return -1;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出while (cin >> s >> n >> m && s && n && m){if (s % 2){cout << "NO\n";continue;}int ans = bfs();if (ans == -1) puts("NO");else cout << ans << endl;}// fclose(stdout);//输出结束return 0;
}

M.Find a way(找"去KFC的"路)

主要知识点:BFS求最短路

题意概括:

地图上标记了两个人和若干个KFC的位置。

这两个人想去同一个KFC,求两个人花的总时间最少为多少。

解题思路:

BFS问题,两次BFS分别预处理两个人到所有KFC的距离,最后求距离之和最小的即可

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;typedef pair<int, int> PII;
typedef pair<int, pair<int, int>> PIII;
const int N = 200 + 10;int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
int n, m;
char g[N][N];
// dis:第一个人到地图上点的最短距离
// d:第二个人到地图上点的最短距离
int dis[N][N], d[N][N];
PII a, b;   // 记录两个人的坐标
// 预处理第一个人到地图上点的最短距离
void bfs1()
{// 求最短距离的时候最好还是用大数标记,否则有的KFC走不到就麻烦了memset(dis, 0x3f, sizeof dis);queue<PII> q1;q1.push(a);dis[a.x][a.y] = 0;while (q1.size()){PII t = q1.front(); q1.pop();int x = t.x , y = t.y;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (a <= 0 || a > n || b <= 0 || b > m) continue;if (g[a][b] == '#') continue;if (dis[a][b] == 0x3f3f3f3f) {dis[a][b] = dis[x][y] + 1;q1.push({a, b});}}}
}
// 预处理第二个人到地图上点的最短距离
void bfs2()
{memset(d, 0x3f, sizeof d);queue<PII> q;q.push(b);d[b.x][b.y] = 0;while (q.size()){PII t = q.front(); q.pop();int x = t.x , y = t.y;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (a <= 0 || a > n || b <= 0 || b > m) continue;if (g[a][b] == '#') continue;if (d[a][b] == 0x3f3f3f3f) {d[a][b] = d[x][y] + 1;q.push({a, b});}}}
}void solve()
{vector<PII> ans; // ans 记录KFC的位置ans.clear();for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1;for (int i = 1; i <= n; i ++ )for (int j  = 1; j <= m; j ++ ){if (g[i][j] == 'Y') a = {i, j};if (g[i][j] == 'M') b = {i, j};if (g[i][j] == '@') ans.push_back({i, j});}bfs1();bfs2();int mn = 1e9;// 分别求所有for (int i = 0; i < ans.size(); i ++ ){int x = ans[i].x, y = ans[i].y;mn = min(mn, dis[x][y] + d[x][y]);}cout << mn * 11 << endl;
}int main()
{// freopen("data.in","r",stdin);  // 文件输入// freopen("data.out","w",stdout);// 文件输出while (cin >> n >> m){solve();}// fclose(stdout);//输出结束return 0;
}

[kuangbin带你飞] 专题一 简单搜索 题解(超详细注释,史上最强题解)相关推荐

  1. poj 3728 Catch That Cow ([kuangbin带你飞]专题一 简单搜索)

    题目大意:题目链接 就是给你N,K,每次有三种惭怍+1,-1,*2,,问多少次操作能到K 解题思路,搜索直接算,.,,,哎,啥时候这种垃圾搜索我能直接A 啊,太菜了 #include<cstdi ...

  2. [kuangbin带你飞]专题1 简单搜索 J - Fire! UVA - 11624

    题目: Joe works in a maze. Unfortunately, portions of the maze have caught on fire, and the owner of t ...

  3. [kuangbin带你飞]专题一 简单搜索D - Fliptile(POJ 3279)

    题目大意 给一个N行M列的矩阵,值分别为0和1,每次你可以选择将一个变成相反状态,同时,它周围的四个数也会变为相反状态. 问:最少翻转多少次,可以将所有值都变成0 多个解,输出翻转次数最少的(若有次数 ...

  4. Catch That Cow POJ - 3278 [kuangbin带你飞]专题一 简单搜索

    Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. ...

  5. kuangbin带你飞专题合集

    题目列表 [kuangbin带你飞]专题一 简单搜索 [kuangbin带你飞]专题二 搜索进阶 [kuangbin带你飞]专题三 Dancing Links [kuangbin带你飞]专题四 最短路 ...

  6. “kuangbin带你飞”专题计划——专题十四:数论基础

    写在前面 1.目前还没啥写的.开始时间:2021-05-13(其实博客上看得到该博客创建时间的) 2.上一个专题刷的是网络流(博客总结),属于第一次接触.本来想的是一周特别高效,然后一周略划水,结果是 ...

  7. (2021-07-14~)“kuangbin带你飞”专题计划——专题十三:基础计算几何

    目录 前言 参考博客 自己总结的东西: 难度判断? 题目 1.[TOYS POJ - 2318 ](解决) 2.[Toy Storage POJ - 2398 ](解决) 3.[Segments PO ...

  8. [kuangbin带你飞]专题十二 基础DP1 题解+总结

    kuangbin带你飞:点击进入新世界 总结: 简单dp,最近在做,持续更新. 文章目录 总结: 1.Max Sum Plus Plus 2.Ignatius and the Princess IV ...

  9. kuangbin带你飞 专题1-23 题单

    kuangbin大神,对于打过ACM比赛的ACMer,无人不知无人不晓. 在此,附上vjudge平台上一位大神整理的[kuangbin带你飞]专题目录链接. [kuangbin带你飞专题目录1-23] ...

  10. [kuangbin带你飞]专题1

    专题一 简单搜索 POJ 1321 棋盘问题 在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别.要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大 ...

最新文章

  1. mysql导出bacpac_数据库的迁移
  2. 你的微信,到底「连接」多少人?
  3. xp/win 7 系统搭建 Java环境
  4. php日志缓存,php – Symfony和Docker – 缓存和日志目录权...
  5. 应该如何理解mobx_如何使用mobx观察observable数组上的object.property更改
  6. AngularJS Documents 官方英文文档
  7. 深鸿会深大小组学习笔记:第一周,从零开发鸿蒙小游戏2048app(上)
  8. metasploit(十)漏洞攻击exploit代码编写
  9. qt打开xls文件_Qt中打开excel文件
  10. Minima黑色响应式后台管理模板
  11. spotify能免费下歌吗_什么是Spotify Duo,它适合您吗?
  12. C语言:求高次方数的尾数
  13. The Shawshank Redemption-5
  14. 做企业数字化转型的最佳拍档,中软国际的变与不变
  15. 个税继续教育证书有哪些 计算机,个税专项附加扣除中继续教育哪些证书可以扣除呢?...
  16. 浅析嵌入式系统的发展趋势
  17. Google Android EDLA协议及AER认证
  18. php网页地图上自定义,网页嵌入百度地图和使用百度地图api自定义地图的详细步骤...
  19. FofaMap云查询版
  20. python函数应用

热门文章

  1. 耶路撒冷三千年(笔记)
  2. 下载文件变成php文档,关于文件下载后变成PHP格式的解决办法
  3. 架构之美第三章-美丽架构之道
  4. 小技巧:Win7屏保变梦幻桌面
  5. 计算机的启动盘,做win7启动盘制作方法
  6. 5y计算机应用2010综合测评答案,计算机二级习题答案
  7. html手机网页新闻模板,新浪手机新闻网站模板首页html源码
  8. 《SAP从入门到精通》——1.3 SAP R/3系统工作原理
  9. 计算判断两条线是否垂直,平行,相交,求相交点坐标
  10. 名字生成器 - 再也不用担心给孩子起名了