8.2 BFS
8.2 BFS
http://codeup.hustoj.com/contest.php?cid=100000609
A Jugs


题目释义
你有两个杯子A、B和无限量的水,杯子容量是Ca、Cb,问如何通过👇操作,使第二杯中有容量为N的水。
能进行的操作是 "fill A", "empty A", "fill B", "empty B", "pour A B", "pour B A"
其中pour A B表示将A中的水倒入B中,直到A为空或者B满
题目解析
BFS题解模板👇

-
这道题有6中操作,所以BFS每一步需选择进行哪种操作,因此就需要记录每一次杯子的容量a、b,通过a、b来判断能进行什么操作。又因为最后要输出所有操作,故每一步选择什么操作都要记录。
- 因此,定义结构体node,含成员a、b、string型的op
- 💡tip:每次记录操作不便于存储,所以定义一个ans[6]数组存放操作,则op只需累加记录每一步操作对应的数就可以
-
定义queue<node> q; 通过inq数组记录每一组a、b的状态是否重复入队
-
根据BFS模板写代码,S表示初始状态,杯中水量a、b都为0。S入队。
- while循环中每次取出队首元素,判断b是否为K(是否满足条件),若是则输出具体操作
- 否则,判断当前a、b容量下能进行什么操作,将对应的元素加入队列
代码
#include <cstdio>
#include <string>
#include <queue>
#include <iostream>
#include <cstring>
#define maxn 1005
using namespace std;
struct node {
int a, b;
string op;
node(int a, int b) {
this->a = a;
this->b = b;
}
node(int a, int b, string op) {
this->a = a;
this->b = b;
this->op = op;
}
};
string ans[6] = {"fill A", "empty A", "fill B", "empty B", "pour A B", "pour B A"};
int ca, cb, n;
node S(0, 0);
bool inq[maxn][maxn];
//a、b为目前两容器的量
void BFS() {
queue<node> q; //定义队列
q.push(S); //首元素入队
inq[0][0] = true;//标记首元素已入队
while (!q.empty()) { //队列非空
node temp = q.front();
q.pop();
if (temp.b == n) { //终止条件是第二杯水满足题目要求
for (int i = 0; i < temp.op.size(); i++) {
cout << ans[temp.op[i] - '0'] << endl;
}
cout << "success" << endl;
return;
}
//装满A
if (!inq[ca][temp.b]) {
node t(ca, temp.b, temp.op + "0");
q.push(t);
inq[ca][temp.b] = true;
}
//倒空A
if (!inq[0][temp.b]) {
node t(0, temp.b, temp.op + "1");
q.push(t);
inq[0][temp.b] = true;
}
//装满B
if (!inq[temp.a][cb]) {
node t(temp.a, cb, temp.op + "2");
q.push(t);
inq[temp.a][cb] = true;
}
//倒空B
if (!inq[temp.a][0]) {
node t(temp.a, 0, temp.op + "3");
q.push(t);
inq[temp.a][0] = true;
}
//从A倒入B
if (temp.a >= cb - temp.b) {
if (!inq[temp.a - (cb - temp.b)][cb]) {
node t(temp.a - (cb - temp.b), cb, temp.op + "4");
q.push(t);
inq[temp.a - (cb - temp.b)][cb] = true;
}
} else if (!inq[0][temp.b + temp.a]) {
node t(0, temp.b + temp.a, temp.op + "4");
q.push(t);
inq[0][temp.b + temp.a] = true;
}
//从B倒入A
if (temp.b >= ca - temp.a) {
if (!inq[ca][temp.b - (ca - temp.a)]) {
node t(ca, temp.b - (ca - temp.a), temp.op + "5");
q.push(t);
inq[ca][temp.b - (ca - temp.a)] = true;
}
} else if (!inq[temp.a + temp.b][0]) {
node t(temp.a + temp.b, 0, temp.op + "5");
q.push(t);
inq[temp.a + temp.b][0] = true;
}
}
}
int main() {
while (scanf("%d%d%d", &ca, &cb, &n) != EOF) {
memset(inq,0, sizeof(inq));
BFS();
}
return 0;
}
B DFS or BFS?

题目解析
题目注意点⚠️
-
可以原地不动
-
因为石头不断下降,所以每次面对的地图情况都不同,所以不用bool数组判断是否已入队
-
每入队一层结点,石头下降一次,所以node结点中需要当前层数step
- 观察用法,不能在while中的for循环中判断flag=true,最后for循环外根据flag判断是否更新地图,因为只有当本层结点都走过后才能更新地图到下一层结点
- 所以通过定义level,判断level < Node.step,然后选择是否更新地图
- 🌰 假设第0层有A,第1层有B、C、D,第2层B后有E、F。
- 🔵 如果通过flag方式,则A入队,for循环中BCD入队,flag=true,for循环结束后因flag=true,则下降一次石头变为第1层状态。
- B出队,for循环中EF入队,flag=true,for循环结束后因flag=true,则下降一次石头变为第2层状态。【此时queue中为CDEF】
- C出队,此时应该是第1层石头的状态,不应该是第2层,所以出错了❌
- 🔵 如果通过level方式,则A入队,for循环中BCD入队
- B出队,此时B的step=1,level=0<1,故下降一层石头变成第1层状态,for循环中EF入队
- C出队,此时C的step=1,level=step,故不下降石头 ✅
-
更新地图只能从后往前更新 for (int i = 7; i >= 0; i--) ,否则从前往后一个石头就变成一列石头了【自己思考下】
-
如果step>=8 肯定能走到终点,因为石头都掉光了(剪枝)
-
每组数据之后还有一个空行,要getchar()
代码
#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
struct node {
int x, y;
int step; //每层掉一次石头
} U, A, Node;
int n;
char a[8][8];
// 上,下,左,右,左上,右上,左下,右下,原地不动
int X[9] = {-1, 1, 0, 0, -1, -1, 1, 1, 0};
int Y[9] = {0, 0, -1, 1, -1, 1, -1, -1, 0};
//判断当前位置是否安全
//不需要判是否已入队,因为石头会下降所以每次都是不同情况
bool judge(int x, int y) {
if (x < 0 || x > 7 || y < 0 || y > 7)return false;
if (a[x][y] == 'S') return false; //当前位置有石头
if (x > 0 && a[x - 1][y] == 'S') return false; //当前位置上方有石头
return true;
}
//更新大石头位置
//!!!!! 要从后往前更新,本来我想的是在先判断这个位置是不是落石,如果是的话,那就把下面一个也变成落石,可是这样的话,就会对地图再一次更新
//就会产生一列都是S的情况
bool refresh() {
for (int i = 7; i >= 0; i--) {
for (int j = 7; j >= 0; j--) {
if (a[i][j] == 'S') {
if (i + 1 < 8) a[i + 1][j] = 'S';
a[i][j] = '.';
}
}
}
}
bool BFS() {
queue<node> q;
q.push(U);
int level = 0;
while (!q.empty()) {
Node = q.front();
q.pop();
if (level < Node.step) {
refresh();
level = Node.step;
}
if ((Node.x == A.x && Node.y == A.y) || Node.step >= 8) return true; //若能撑过8步,则石头都掉完了,肯定能到达终点
bool flag=false;
for (int i = 0; i < 9; i++) {
int newX = Node.x + X[i];
int newY = Node.y + Y[i];
//当前位置安全
if (judge(newX, newY)) {
flag=true;
node temp;
temp.x = newX, temp.y = newY, temp.step = Node.step + 1;
q.push(temp);
}
}
}
return false;
}
int main() {
scanf("%d", &n);
getchar();
int time = n;
U.x = 7, U.y = 0, U.step = 0;
A.x = 0, A.y = 7;
while (n--) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
scanf("%c", &a[i][j]);
}
getchar();//去掉换行
}
getchar(); //每组数据后还有一个空行要去掉
if (BFS()) printf("Case #%d: Yes\n", time - n);
else printf("Case #%d: No\n", time - n);
}
return 0;
}
C 【宽搜入门】8数码难题

题目解析
⚠️ 这道题中,不断移动的是空行,每次空格0能否上下左右移动,除了要判断碰到墙壁的情况,还要判断当前9个数字的状态是否重复。
-
因为移动的是空格(题中用0表示),所以需记录空格的位置x、y。为了记录每次9中数字的状态,使用vector<int>,判断状态是否重复采用map<vector<int>, bool>映射。最后要输出步数,所以也需要记录step
-
S表示开始状态,T表示目的状态。
-
队列中每次取出的队首元素如果状态和T相同,则输出步数;
否则看当前0能移动的位置,判断此时的状态序列是否重复,若否则加入队列
-
❗️因为vector是一维数组,0记录的是在二维数组中的位置,所以转换每次0移动后转换为序列时要注意换算。
- 若0的坐标(x,y)转换到一维数组中是status[3*x+y]
- 🌰 输入样例中“2,8,3,1,6,4,7,0,5”中0的坐标是(2,1),在status中是status[7]
- 若0向上移动X[i]=-1,则status中对应下标-3
- 若0向下移动X[i]=1,则status中对应下标+3
- 若0向左移动Y[i]=-1,则status中对应下标-1
- 若0向右移动Y[i]=1,则status中对应下标+1
- 若0的坐标(x,y)转换到一维数组中是status[3*x+y]
代码
#include <cstdio>
#include <string>
#include <vector>
#include <iostream>
#include <queue>
#include <map>
#include <algorithm>
using namespace std;
struct node {
vector<int> status;
int x, y, step;
} S, T, Node;
map<vector<int>, bool> hashtable;
int X[4] = {-1, 1, 0, 0};
int Y[4] = {0, 0, -1, 1};
bool check(node a, node b) {
for (int i = 0; i < 9; i++) {
if (a.status[i] != b.status[i]) return false;
}
return true;
}
int BFS() {
queue<node> q;
q.push(S);
hashtable[S.status] = true;
while (!q.empty()) {
Node = q.front();
q.pop();
if (check(Node, T)) return Node.step;
for (int i = 0; i < 4; i++) {
node temp;
temp.x = Node.x + X[i];
temp.y = Node.y + Y[i];
if (temp.x < 0 || temp.x > 2 || temp.y < 0 || temp.y > 2) continue;
// 得到新的序列
temp.status = Node.status;
int pos_0 = 3 * Node.x + Node.y;
if (X[i] == 1) {
swap(temp.status[pos_0], temp.status[pos_0 + 3]);
pos_0 += 3;
} else if (X[i] == -1) {
swap(temp.status[pos_0], temp.status[pos_0 - 3]);
pos_0 -= 3;
}
if (Y[i] == 1) {
swap(temp.status[pos_0], temp.status[pos_0 + 1]);
} else if (Y[i] == -1) {
swap(temp.status[pos_0], temp.status[pos_0 - 1]);
}
if (!hashtable[temp.status]) {
temp.step = Node.step + 1;
hashtable[temp.status] = true;
q.push(temp);
}
}
}
}
int main() {
int temp;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
scanf("%d", &temp);
S.status.push_back(temp);
if (temp == 0) S.x = i, S.y = j;
}
}
S.step = 1;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
scanf("%d", &temp);
T.status.push_back(temp);
if (temp == 0) T.x = i, T.y = j;
}
}
printf("%d\n", BFS());
return 0;
}
D 【宽搜入门】魔板

题目解析
与C题差不多,都需要记录每次的总体数字状态(异曲同工的感觉🙃)
⚠️ 这道题中,魔板每次都能进行A、B、C三种操作,要判断魔板的状态是否重复。
- 因为最后要求输出具体的操作,所以使用string记录每次操作,为了记录每次魔板状态,使用vector<int>,判断状态是否重复采用map<vector<int>, bool>映射。最后要输出步数,所以也需要记录step
- ❗️开始状态总是 1 2 3 4 5 6 7 8(是顺时针取的数),最终要达到输入的状态。
- 当心每次A、B、C操作的状态转换:
- 🌰 从基本状态 1 2 3 4 5 6 7 8作A转换 -》8 7 6 5 4 3 2 1
- 🌰 从基本状态 1 2 3 4 5 6 7 8作B转换 -》4 1 2 3 6 7 8 5
- 🌰 从基本状态 1 2 3 4 5 6 7 8作C转换 -》1 7 2 4 5 3 6 8
- ❗️题目要求输出除最后一行外,每行输出60个字符
代码
//
// Created by 王怡静 on 2020/8/24.
//
#include <iostream>
#include <cstdio>
#include <vector>
#include <map>
#include <string>
#include <queue>
using namespace std;
struct node {
vector<int> status;
string op;
int step;
} S, T, Node;
map<vector<int>, bool> hashtable;
bool check(node a, node b) {
for (int i = 0; i < 8; i++) {
if (a.status[i] != b.status[i]) return false;
}
return true;
}
void BFS() {
queue<node> q;
q.push(S);
hashtable[S.status] = true;
while (!q.empty()) {
Node = q.front();
q.pop()
if (check(Node, T)) {
printf("%d\n", Node.step);
for (int i = 1; i <= Node.op.size(); i++) {
cout << Node.op[i-1];
if (i % 60 == 0) cout << endl;
}
cout << endl;
return;
}
// 操作A——交换上下两行
node temp1 = node();
for (int i = 7; i >= 0; i--) {
temp1.status.push_back(Node.status[i]);
}
if (!hashtable[temp1.status]) {
temp1.op = Node.op + "A";
temp1.step = Node.step + 1;
hashtable[temp1.status] = true;
q.push(temp1);
}
//操作B——将最右边的一列插入最左边
node temp2 = node();
temp2.status.push_back(Node.status[3]);
for (int i = 0; i < 3; i++) temp2.status.push_back(Node.status[i]);
for (int i = 5; i < 8; i++) temp2.status.push_back(Node.status[i]);
temp2.status.push_back(Node.status[4]);
if (!hashtable[temp2.status]) {
temp2.op = Node.op + "B";
temp2.step = Node.step + 1;
hashtable[temp2.status] = true;
q.push(temp2);
}
//操作C——魔板中央四格作顺时针旋转
node temp3 = node();
temp3.status.push_back(Node.status[0]);
temp3.status.push_back(Node.status[6]);
temp3.status.push_back(Node.status[1]);
temp3.status.push_back(Node.status[3]);
temp3.status.push_back(Node.status[4]);
temp3.status.push_back(Node.status[2]);
temp3.status.push_back(Node.status[5]);
temp3.status.push_back(Node.status[7]);
if (!hashtable[temp3.status]) {
temp3.op = Node.op + "C";
temp3.step = Node.step + 1;
hashtable[temp3.status] = true;
q.push(temp3);
}
}
}
int main() {
int temp;
for (int i = 1; i <= 8; i++) S.status.push_back(i);
S.step = 0;
for (int i = 0; i < 8; i++) {
scanf("%d", &temp);
T.status.push_back(temp);
}
BFS();
return 0;
}
E 【宽搜入门】巧妙取量

题目解析
这道题其实和A题倒水性质是一样的,区别在于,这道题一开始的水量是满杯A的水,没有无限的水给你装。最后判断三个杯子中一个是要求的K水量就可以。
所以操作就6种:A倒B、A倒C、B倒A、B倒C、C倒A、C倒B。
-
BFS每一步需选择进行哪种操作,因此就需要记录每一次杯子的容量a、b、c,因为最后只需要输出最少的步骤,所以再存一下step就可以
-
定义queue<node> q; 通过inq数组记录每一组a、b、c的状态是否重复入队
-
根据BFS模板写代码,S表示初始状态,杯中水量a为容量ca,b和c都为0。S入队。
- while循环中每次取出队首元素,判断Node.a==k || Node.b==k|| Node.c==k 若是则return Node.step
- 否则,判断当前a、b、c容量下能进行什么操作,将对应的元素加入队列
代码
#include <cstdio>
#include <queue>
#include <cstring>
#define maxn 105
using namespace std;
int ca, cb, cc, k;
struct node {
int a, b, c;
int step;
node() {}
node(int a, int b, int c, int step) : a(a), b(b), c(c), step(step) {}
} S, Node;
bool inq[maxn][maxn][maxn];
int BFS() {
queue<node> q;
memset(inq, 0, sizeof(inq));
q.push(S);
inq[S.a][S.b][S.c] = true;
while (!q.empty()) {
Node = q.front();
q.pop();
if(Node.a==k || Node.b==k|| Node.c==k) return Node.step;
// A->B
if (Node.a >= cb - Node.b) {
if (!inq[Node.a - (cb - Node.b)][cb][Node.c]) {
node temp(Node.a - (cb - Node.b), cb, Node.c, Node.step + 1);
inq[Node.a - (cb - Node.b)][cb][Node.c] = true;
q.push(temp);
}
} else if (!inq[0][Node.a + Node.b][Node.c]) {
node temp(0, Node.a + Node.b, Node.c, Node.step + 1);
inq[0][Node.a + Node.b][Node.c] = true;
q.push(temp);
}
// A->C
if (Node.a >= cc - Node.c) {
if (!inq[Node.a - (cc - Node.c)][Node.b][cc]) {
node temp(Node.a - (cc - Node.c), Node.b, cc, Node.step + 1);
inq[Node.a - (cc - Node.c)][Node.b][cc] = true;
q.push(temp);
}
} else if (!inq[0][Node.b][Node.a + Node.c]) {
node temp(0, Node.b, Node.a + Node.c, Node.step + 1);
inq[0][Node.b][Node.a + Node.c] = true;
q.push(temp);
}
// B->A
if (Node.b >= ca - Node.a) {
if (!inq[ca][Node.b - (ca - Node.a)][Node.c]) {
node temp(ca, Node.b - (ca - Node.a), Node.c, Node.step + 1);
inq[ca][Node.b - (ca - Node.a)][Node.c] = true;
q.push(temp);
}
} else if (!inq[Node.a + Node.b][0][Node.c]) {
node temp(Node.a + Node.b, 0, Node.c, Node.step + 1);
inq[Node.a + Node.b][0][Node.c] = true;
q.push(temp);
}
// B->C
if (Node.b >= cc - Node.c) {
if (!inq[Node.a][Node.b - cc + Node.c][cc]) {
node temp(Node.a, Node.b - cc + Node.c, cc, Node.step + 1);
inq[Node.a][Node.b - cc + Node.c][cc] = true;
q.push(temp);
}
} else if (!inq[Node.a][0][Node.b + Node.c]) {
node temp(Node.a, 0, Node.b + Node.c, Node.step + 1);
inq[Node.a][0][Node.b + Node.c] = true;
q.push(temp);
}
//C->A
if (Node.c >= ca - Node.a) {
if (!inq[ca][Node.b][Node.c - ca + Node.a]) {
node temp(ca, Node.b, Node.c - ca + Node.a, Node.step + 1);
inq[ca][Node.b][Node.c - ca + Node.a] = true;
q.push(temp);
}
} else if (!inq[Node.a + Node.c][Node.b][0]) {
node temp(Node.a + Node.c, Node.b, 0, Node.step + 1);
inq[Node.a + Node.c][Node.b][0] = true;
q.push(temp);
}
//C->B
if (Node.c >= cb - Node.b) {
if (!inq[Node.a][cb][Node.c - cb + Node.b]) {
node temp(Node.a, cb, Node.c - cb + Node.b, Node.step + 1);
inq[Node.a][cb][Node.c - cb + Node.b] = true;
q.push(temp);
}
} else if (!inq[Node.a][Node.b + Node.c][0]) {
node temp(Node.a, Node.b + Node.c, 0, Node.step + 1);
inq[Node.a][Node.b + Node.c][0] = true;
q.push(temp);
}
}
return -1;
}
int main() {
while (scanf("%d%d%d%d", &ca, &cb, &cc, &k) != EOF) {
S.a = ca, S.b = 0, S.c = 0, S.step = 0;
int ans=BFS();
if(ans!=-1){
printf("yes\n%d\n",ans);
}else printf("no\n");
}
return 0;
}
本文作者:Joey-Wang
本文链接:https://www.cnblogs.com/joey-wang/p/14541180.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步