曾经沧海难为水,除却巫山不是云。|

Joey-Wang

园龄:4年3个月粉丝:17关注:0

8.2 BFS

8.2 BFS

http://codeup.hustoj.com/contest.php?cid=100000609

A Jugs

image-20200825011917985 image-20200825011952480

题目释义

你有两个杯子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题解模板👇

image-20200825012235783
  1. 这道题有6中操作,所以BFS每一步需选择进行哪种操作,因此就需要记录每一次杯子的容量a、b,通过a、b来判断能进行什么操作。又因为最后要输出所有操作,故每一步选择什么操作都要记录。

    • 因此,定义结构体node,含成员a、b、string型的op
    • 💡tip:每次记录操作不便于存储,所以定义一个ans[6]数组存放操作,则op只需累加记录每一步操作对应的数就可以
  2. 定义queue<node> q; 通过inq数组记录每一组a、b的状态是否重复入队

  3. 根据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?

image-20200825013705700

题目解析

题目注意点⚠️

  1. 可以原地不动

  2. 因为石头不断下降,所以每次面对的地图情况都不同,所以不用bool数组判断是否已入队

  3. 每入队一层结点,石头下降一次,所以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,故不下降石头 ✅
  4. 更新地图只能从后往前更新 for (int i = 7; i >= 0; i--) ,否则从前往后一个石头就变成一列石头了【自己思考下】

  5. 如果step>=8 肯定能走到终点,因为石头都掉光了(剪枝)

  6. 每组数据之后还有一个空行,要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数码难题

image-20200825014955081

题目解析

⚠️ 这道题中,不断移动的是空行,每次空格0能否上下左右移动,除了要判断碰到墙壁的情况,还要判断当前9个数字的状态是否重复。

  1. 因为移动的是空格(题中用0表示),所以需记录空格的位置x、y。为了记录每次9中数字的状态,使用vector<int>,判断状态是否重复采用map<vector<int>, bool>映射。最后要输出步数,所以也需要记录step

  2. S表示开始状态,T表示目的状态。

  3. 队列中每次取出的队首元素如果状态和T相同,则输出步数;

    否则看当前0能移动的位置,判断此时的状态序列是否重复,若否则加入队列

  4. ❗️因为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

代码

#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 【宽搜入门】魔板

image-20200825020323065

题目解析

与C题差不多,都需要记录每次的总体数字状态(异曲同工的感觉🙃)

⚠️ 这道题中,魔板每次都能进行A、B、C三种操作,要判断魔板的状态是否重复。

  1. 因为最后要求输出具体的操作,所以使用string记录每次操作,为了记录每次魔板状态,使用vector<int>,判断状态是否重复采用map<vector<int>, bool>映射。最后要输出步数,所以也需要记录step
  2. ❗️开始状态总是 1 2 3 4 5 6 7 8(是顺时针取的数),最终要达到输入的状态。
  3. 当心每次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
  1. ❗️题目要求输出除最后一行外,每行输出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 【宽搜入门】巧妙取量

image-20200825021349019

题目解析

这道题其实和A题倒水性质是一样的,区别在于,这道题一开始的水量是满杯A的水,没有无限的水给你装。最后判断三个杯子中一个是要求的K水量就可以。
所以操作就6种:A倒B、A倒C、B倒A、B倒C、C倒A、C倒B。

  1. BFS每一步需选择进行哪种操作,因此就需要记录每一次杯子的容量a、b、c,因为最后只需要输出最少的步骤,所以再存一下step就可以

  2. 定义queue<node> q; 通过inq数组记录每一组a、b、c的状态是否重复入队

  3. 根据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 中国大陆许可协议进行许可。

posted @   Joey-Wang  阅读(48)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开