2021牛客暑期多校第二场I题(企鹅)题解

企鹅游戏

题目大意

有一种移动企鹅的游戏,左边有一张20×20的地图,右边也有一张20×20的地图。地图中‘#’表示障碍,‘.’表示无障碍。游戏开始时,左边地图中的企鹅位于地图右下角,右边地图中的企鹅位于地图左下角。

玩家每次可以控制左边或右边的企鹅上下左右移动,两只企鹅的移动方向是镜像的。如果某只企鹅在其移动方向上会超出地图边界或遇到障碍,则移动该企鹅后不改变位置;而另一只企鹅如果在相对镜像的移动方向上没有超出地图边界或遇到障碍,则移动后改变位置。

现在需要你求出最小移动步数,和某只企鹅在最小移动步数下的最小字典序移动方案,使得左边企鹅移动到左边地图右上角,右边企鹅移动到右边地图左上角;并将移动路径在两张地图上用‘A’标记后,打印两张地图。

样例输入

#................... .............##...#.
.................... .......#.....#.....#
.........#...#.#.... ...#....#...........
#........#.......... ...#..#.............
........#......#.... ..#.#......#.#.....#
......#.#..#.#....#. .......##.....##...#
....#...........#..# ....................
.##................. ...........#..#...#.
.....#.#........#.#. #.........#.#.......
.................... ..#....#..........#.
....#.#..........#.. .#.........#..#..#..
.........#.......#.. ..#.................
...#..#......#...#.. ......#.............
...........#...#.... ....................
..##..#.#....#..#... ..............#...#.
.#..#...#.#.....##.. .........#.#...#....
.#.........#........ ..............#.#...
..##.#........#...#. ##..................
....##.#............ .......#.....#......
..........##........ .#..#.#...........#.

样例输出

27
LULLUURRUUUUUUULUUUUURRUUUU
#..................A A............##...#.
...................A A......#.....#.....#
.........#...#.#...A A..#....#...........
#........#.........A A..#..#.............
........#......#.AAA AA#.#......#.#.....#
......#.#..#.#...A#. .A.....##.....##...#
....#...........#A.# .A..................
.##..............A.. .A.........#..#...#.
.....#.#........#A#. #A........#.#.......
.................AA. AA#....#..........#.
....#.#..........#A. A#.........#..#..#..
.........#.......#A. A.#.................
...#..#......#...#A. A.....#.............
...........#...#..A. A...................
..##..#.#....#..#.A. A.............#...#.
.#..#...#.#.....##A. A........#.#...#....
.#.........#....AAA. AAA...........#.#...
..##.#........#.A.#. ##A.................
....##.#........AAA. AAA....#.....#......
..........##......AA A#..#.#...........#.

思路

题目要求按照最小字典序输出,即“DLRU”。由于左边企鹅至少需要先向左移动,故左边企鹅移动的字典序一定要小于右边企鹅,故只记录左边企鹅的移动方向,右边企鹅使用镜像的方向移动即可。

求最小步数一定使用广度优先搜索,由BFS的性质知,只要按照“DLRU”,即“下左右上”的顺序搜索当前每一种可能的状态并保存即可。由于要求打印答案,故用数组记录每一种状态的父亲状态(类似并查集),搜索深度即为所求步数,使用深度优先搜索递归打印答案即可。

注意由于队列中的状态在后续DFS打印答案时需要使用,而BFS时队列中的状态不会被保存,而是会被弹出,故使用数组保存状态,并用头指针和尾指针模拟队列。具体细节见代码。

考点

深度优先搜索
广度优先搜索
模拟

AC代码(含注释)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 20 + 5;
char mpl[maxn][maxn];//左边地图
char mpr[maxn][maxn];//右边地图
string dir="DLRU";//按照从0~3表示下左右上
int dlx[4] = {1,0,0,-1};//左边企鹅方向
int dly[4] = {0,-1,1,0};
int drx[4] = {1,0,0,-1};//右边企鹅镜像方向
int dry[4] = {0,1,-1,0};
bool vis[maxn][maxn][maxn][maxn];//判重
struct Node {
    int lx,ly,rx,ry,nd;//下面使用结构体构造函数,注意初始化
    Node(int a=0,int b=0,int c=0,int d=0,int e=0):lx(a),ly(b),rx(c),ry(d),nd(e){}
}inq[maxn*maxn*maxn*maxn];//inq数组模拟队列,保存所有状态
int fa[maxn*maxn*maxn*maxn];//记录父亲节点
int slx=20,sly=20,elx=1,ely=20;//初始位置
int srx=20,sry=1,erx=1,ery=1;//最终位置
int head=0,tail=0;//头指针,尾指针,模拟队首队尾
void dfs(int now,int deep){
    mpl[inq[now].lx][inq[now].ly]='A';
    mpr[inq[now].rx][inq[now].ry]='A';
    if(!now){
        cout<<deep<<endl;return;//当前为第0个节点,搜索结束
    }
    dfs(fa[now],deep+1);//递归搜索父亲节点,深度加1
    cout<<dir[inq[now].nd];//回溯打印方向
}
void bfs(){
    inq[0]=Node(slx,sly,srx,sry);//初始化第0个节点
    for(;head<=tail;head++){//模拟队列,头指针指向当前节点,尾指针指向队尾
        if(inq[head].lx==elx&&inq[head].ly==ely&&inq[head].rx==erx&&inq[head].ry==ery){
            dfs(head,0);return;//最先搜索到了最终状态,从当前节点开始递归打印解
        }
        for(int i=0;i<4;i++){
            int nlx=inq[head].lx+dlx[i];
            int nly=inq[head].ly+dly[i];
            if(nlx>20||nlx<1||nly>20||nly<1||mpl[nlx][nly]=='#'){
                nlx-=dlx[i],nly-=dly[i];//越界或有障碍,不改变位置
            }
            int nrx=inq[head].rx+drx[i];
            int nry=inq[head].ry+dry[i];
            if(nrx>20||nrx<1||nry>20||nry<1||mpr[nrx][nry]=='#'){
                nrx-=drx[i],nry-=dry[i];//越界或有障碍,不改变位置
            }
            if(vis[nlx][nly][nrx][nry])continue;//判重
            vis[nlx][nly][nrx][nry]=true;
            inq[++tail]=Node(nlx,nly,nrx,nry,i);//保存状态
            fa[tail]=head;//记录当前节点的父亲节点
        }
    }
}
int main(){
    string s1,s2;
    for(int i=1;i<=20;i++){
        cin>>s1>>s2;
        for(int j=0;j<20;j++){
            mpl[i][j+1]=s1[j],mpr[i][j+1]=s2[j];//存图
        }
    }
    bfs();
    cout<<endl;
    for(int i=1;i<=20;i++){
        for(int j=1;j<=20;j++)cout<<mpl[i][j];
        cout<<" ";
        for(int j=1;j<=20;j++)cout<<mpr[i][j];
        cout<<endl;
    }
    return 0;
}

心得

这道题不好想的地方,一是利用BFS将节点单层逐个入队的性质求最小字典序,二是如何用数组模拟队列并保存状态,三是如何利用深搜打印答案。容易粗心的地方在于模拟镜像的过程。本题考察的关于搜索的知识点算是相当全面,是一道好题,值得研究透彻。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

keguaiguai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值