《算法竞赛进阶指南》2.5广度优先搜索

172. 立体推箱子

立体推箱子是一个风靡世界的小游戏。

游戏地图是一个N行M列的矩阵,每个位置可能是硬地(用”.”表示)、易碎地面(用”E”表示)、禁地(用”#”表示)、起点(用”X”表示)或终点(用”O”表示)。

你的任务是操作一个1×1×2的长方体。

这个长方体在地面上有两种放置形式,“立”在地面上(1×1的面接触地面)或者“躺”在地面上(1×2的面接触地面)。

在每一步操作中,可以按上下左右四个键之一。

按下按键之后,长方体向对应的方向沿着棱滚动90度。

任意时刻,长方体不能有任何部位接触禁地,并且不能立在易碎地面上。

字符”X”标识长方体的起始位置,地图上可能有一个”X”或者两个相邻的”X”。

地图上唯一的一个字符”O”标识目标位置。

求把长方体移动到目标位置(即立在”O”上)所需要的最少步数。

在移动过程中,”X”和”O”标识的位置都可以看作是硬地被利用。

输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包括两个整数N和M。
接下来N行用来描述地图,每行包括M个字符,每个字符表示一块地面的具体状态。
当输入用例N=0,M=0时,表示输入终止,且该用例无需考虑。

输出格式
每个用例输出一个整数表示所需的最少步数,如果无解则输出”Impossible”。
每个结果占一行。

数据范围
3≤N,M≤500

输入样例:
7 7
#######
#…X###
#…##O#
#…E#
#…E#
#…#
#######
0 0

输出样例:
10

/*
立着:0
横着躺:1
竖着躺:2

坐标(x,y)

500 * 500 * 3  宽搜

*/

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 510;

struct State
{
    int x, y, lie;
};

int n,m;
char g[N][N];
int dist[N][N][3];//从起始状态到现在这个状态的距离
 
bool check(int x, int y) //判断是不是出界
{
    if(x < 0 || x >= n || y < 0 || y >= m) return false;
    return g[x][y] != '#';
}

int bfs(State start, State end)
{
    memset(dist, -1, sizeof dist);
    dist[start.x][start.y][start.lie] = 0;//起点
    queue<State> q;
    q.push(start);
    
    int d[3][4][3] = {
        {{-2, 0, 2}, {0, 1, 1}, {1, 0, 2}, {0, -2, 1}},  //0 立着   上右下左倒
        {{-1, 0, 1}, {0, 2, 0}, {1, 0, 1}, {0, -1, 0}},  //1 横着躺
        {{-1, 0, 0}, {0, 1, 2}, {2, 0, 0}, {0, -1, 2}},  //2 立着躺
    };
    
    
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        //扩展t
        for(int i = 0; i < 4; i++)
        {
            State next;
            next = {t.x + d[t.lie][i][0], t.y + d[t.lie][i][1], d[t.lie][i][2]};
            
            int x = next.x, y = next.y;
            if(!check(x, y)) continue;//当前不合法 继续下一次循环
            if(next.lie == 0 && g[x][y] == 'E') continue;
            if(next.lie == 1 && !check(x, y + 1)) continue;
            if(next.lie == 2 && !check(x + 1, y)) continue;
            
            if(dist[x][y][next.lie] == -1)
            {
                dist[x][y][next.lie] = dist[t.x][t.y][t.lie] + 1;
                q.push(next);
            }
        }
        
    }
    return dist[end.x][end.y][end.lie];
}

int main()
{
    while(scanf("%d%d", &n, &m), n || m)
    {
        for(int i = 0; i < n; i ++) cin >> g[i];
        
        State start = {-1}, end;
        for(int i = 0; i < n; i ++)
            for(int j = 0; j < m; j++)
                if(g[i][j] == 'X' && start.x == -1)//第一次找到X 赋值起点
                {
                    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};
                }
                else if(g[i][j] == 'O') end = {i, j, 0}; //这里是大o 终点
                
        int res = bfs(start, end);
        if(res == -1) puts("Impossible");
        else printf("%d\n", res);
    }
    return 0;
}

173. 矩阵距离

给定一个N行M列的01矩阵A,A[i][j] 与 A[k][l] 之间的曼哈顿距离定义为:

dist(A[i][j],A[k][l])=|i−k|+|j−l|

输出一个N行M列的整数矩阵B,其中:

B[i][j]=min1≤x≤N,1≤y≤M,A[x][y]=1 dist(A[i][j],A[x][y])

输入格式
第一行两个整数n,m。

接下来一个N行M列的01矩阵,数字之间没有空格。

输出格式
一个N行M列的矩阵B,相邻两个整数之间用一个空格隔开。

数据范围
1≤N,M≤1000

输入样例:
3 4
0001
0011
0110

输出样例:
3 2 1 0
2 1 0 0
1 0 0 1

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;

typedef pair<int, int>PII;
const int N = 1010;

int n, m; //表示矩阵的长和宽
char g[N][N]; //储存矩阵
int d[N][N]; //存储每个点到最近1的距离

void bfs()
{
    memset(d, -1, sizeof d); //d初始化为-1
    queue<PII> q;
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < m; j++)
            if(g[i][j] == '1')
            {
                q.push({i, j});
                d[i][j] = 0; //1到1的距离为1
            }
    //遍历四个方向
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; //上右下左 顺时针
    while (q.size())
    {
        auto t = q.front();
        q.pop();
        
        int x = t.first, y = t.second;
        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 && d[a][b] == -1)
            {
                d[a][b] = d[x][y] + 1;
                q.push({a, b});
            }
        }
    }
    
}

int main()
{
    scanf("%d%d", &n, &m); //读入长和宽
    for(int i = 0; i < n; i ++) scanf("%s", g[i]); //读入矩阵
    
    bfs();
    
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0; j < m; j++)
            printf("%d ", d[i][j]);
        puts("");    
    }
    return 0;
}

174. 推箱子

推箱子游戏相信大家都不陌生,在本题中,你将控制一个人把1个箱子到目的地。

给定一张N行M列的地图,用字符”.”表示空地,字符”#”表示墙,字符”S”表示人的起始位置,字符”B”表示箱子的起始位置,字符”T”表示箱子的目标位置。

求一种移动方案,使箱子移动的次数最少,在此基础上再让人移动的总步数最少。

方案中使用大写的“EWSN”(东西南北)表示箱子的移动,使用小写的“ewsn”(东西南北)表示人的移动。

推箱子.jpg++++++++++++++++

输入格式
输入包含多个测试用例。
对于每个测试用例,第一行包括两个整数N,M。
接下来N行,每行包括M个字符,用以描绘整个N行M列的地图。
当样例为N=0,M=0时,表示输入终止,且该样例无需处理。

输出格式
对于每个测试用例,第一行输出”Maze #”+测试用例的序号。
第二行输入一个字符串,表示推箱子的总体移动过程,若无解,则输出”Impossible.”。
每个测试用例输出结束后输出一个空行。
若有多条路线满足题目要求,则按照N、S、W、E的顺序优先选择箱子的移动方向(即先上下推,再左右推)。
在此前提下,再按照n、s、w、e的顺序优先选择人的移动方向(即先上下动,再左右动)。

数据范围
1≤N,M≤20

输入样例:
1 7
SB…T
1 7
SB…#.T
7 11
###########
#T##…#
#.#.#…####
#…B…#
#.######…#
#…S…#
###########
8 4

.##.
.#…
.#…
.#.B
.##S

###T
0 0

输出样例:
Maze #1
EEEEE

Maze #2
Impossible.

Maze #3
eennwwWWWWeeeeeesswwwwwwwnNN

Maze #4
swwwnnnnnneeesssSSS

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>

using namespace std;

typedef pair<int, int>PII;

const int N = 25;

struct Node
{
    int x, y, dir;
};

int n, m; //矩阵长和宽
char g[N][N]; //表示整个地图
Node pre[N][N][4]; //记录上一步的状态
vector<int> path[N][N][4]; //存储人的路径
bool st[N][N][4], used[N][N]; //每个状态是否被遍历过  人的状态是不是被遍历过
PII dist[N][N][4]; //人的距离
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1}; //上下左右方向推 往上推 去下,第一个为1
int pre_man[N][N]; //上一个人的状态

bool check(int x, int y) //判断是不是出了边界
{
    return x >= 0 && x < n && y >= 0 && y < m && g[x][y] != '#';
}

int bfs_man(PII start, PII end, PII box, vector<int>&seq)
{
    memset(used, false, sizeof used);
    memset(pre_man, -1, sizeof pre_man);
    
    queue<PII> q;
    q.push(start);
    used[start.first][start.second] = true;
    used[box.first][box.second] = true;
    
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        if(t == end)
        {
            seq.clear();
            int x = t.first, y = t.second;
            while(pre_man[x][y] != -1)
            {
                int dir = pre_man[x][y] ^ 1;
                seq.push_back(dir);
                x += dx[dir], y += dy[dir];
            }
            return seq.size();
        }
        for(int i = 0; i < 4; i++) //枚举四个方向
        {
            int x = t.first, y = t.second;
            int a = x + dx[i], b = y + dy[i];
            if(check(a, b) && !used[a][b])
            {
                used[a][b] = true;
                pre_man[a][b] = i;
                q.push({a, b});
            }
        }
    }
    return -1;
}

bool bfs_box(PII man, PII box, Node &end)
{
    memset(st, false, sizeof st); //状态初始化为false
   //memset(pre, -1, sizeof pre); // 上一个状态初始化为-1
    
    queue<Node>q;
    for(int i = 0; i < 4; i++) //初始状态可以枚举的方向
    {
        int x = box.first, y = box.second; //箱子的位置
        int a = x + dx[i], b = y + dy[i]; //要推箱子人所在的位置
        int j = x - dx[i], k = y - dy[i]; //箱子推到的位置
        vector<int> seq;
        if(check(a,b) && check(j,k) && bfs_man(man, {a,b}, box, seq) != -1)
        {
            st[j][k][i] = true;
            q.push({j, k, i});
            dist[j][k][i] = {1, seq.size()}; //箱子移动的距离是1,人移动的距离是seq.size()
            path[j][k][i] = seq;
            pre[j][k][i] = {x, y, -1};
        }
    }
    
    bool success = false;
    PII man_d = {1e9, 1e9};
    
    
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        if(g[t.x][t.y] == 'T')
        {
            success = true;
            
            if(dist[t.x][t.y][t.dir] < man_d)
            {
                man_d = dist[t.x][t.y][t.dir];
                end = t;
            }
        }
        
        for(int i = 0; i < 4; i++) //更新 枚举四个方向
        {
            int a = t.x + dx[i], b = t.y + dy[i]; //要推箱子人所在的位置
            int j = t.x - dx[i], k = t.y - dy[i]; //箱子推到的位置
            if(check(a, b) && check(j, k))
            {
                vector<int> seq; //人走的序列
                auto &p = dist[j][k][i];
                int distance = bfs_man({t.x + dx[t.dir], t.y + dy[t.dir]}, {a, b}, {t.x, t.y}, seq);
                if(distance != -1) //可以走过去 更新状态
                {
                    PII td = {dist[t.x][t.y][t.dir].first + 1, dist[t.x][t.y][t.dir].second + distance};//可以更新的距离
                    if(!st[j][k][i])
                    {
                        st[j][k][i] = true;
                        q.push({j, k, i});
                        path[j][k][i] = seq;
                        pre[j][k][i] = t;
                        p = td;
                    }
                     else if(p > td)
                     {
                        p = td;
                        path[j][k][i] = seq;
                        pre[j][k][i] = t;
                     }
                }
            }
        }
    }
    return success;
}

int main()
{
    int T = 1;
    while(cin >> n >> m, n || m)
    {
        for(int i = 0; i < n; i ++) cin >> g[i];
        
        printf("Maze #%d\n", T++);
        
        PII man, box;//找到人和箱子的起始位置
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                if(g[i][j] == 'S') man = {i, j};
                else if(g[i][j] == 'B') box = {i, j};
        Node end; //终点状态
        
        if(!bfs_box(man, box, end)) puts("Impossible.");
        else
        {
            char ops[] = "nswe";
            string res;
            while(!(end.x == box.first && end.y == box.second)) //不等于起点
            {
                res += ops[end.dir] - 32;
                for(auto dir : path[end.x][end.y][end.dir]) res += ops[dir];
                end = pre[end.x][end.y][end.dir];
            }
            reverse(res.begin(), res.end());
            cout << res << endl;
        }
        puts("");
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值