AcWing1137拯救大兵瑞恩(双端队列搜索,状态压缩,分层图最短路)

题目地址https://www.acwing.com/problem/content/1133/

题目描述

1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。

瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图。

迷宫的外形是一个长方形,其南北方向被划分为 N 行,东西方向被划分为 M 列, 于是整个迷宫被划分为 N×M 个单元。

每一个单元的位置可用一个有序数对 (单元的行号, 单元的列号) 来表示。

南北或东西方向相邻的 2 个单元之间可能互通,也可能有一扇锁着的门,或者是一堵不可逾越的墙。

注意: 门可以从两个方向穿过,即可以看成一条无向边。

迷宫中有一些单元存放着钥匙,同一个单元可能存放 多把钥匙,并且所有的门被分成 PP 类,打开同一类的门的钥匙相同,不同类门的钥匙不同。

大兵瑞恩被关押在迷宫的东南角,即 (N,M) 单元里,并已经昏迷。

迷宫只有一个入口,在西北角。

也就是说,麦克可以直接进入 (1,1) 单元。

另外,麦克从一个单元移动到另一个相邻单元的时间为 1,拿取所在单元的钥匙的时间以及用钥匙开门的时间可忽略不计。

试设计一个算法,帮助麦克以最快的方式到达瑞恩所在单元,营救大兵瑞恩。

输入格式

第一行有三个整数,分别表示 N,M,P 的值。

第二行是一个整数 kk,表示迷宫中门和墙的总数。

接下来 kk 行,每行包含五个整数,Xi1,Yi1,Xi2,Yi2,Gi:当 Gi1 时,表示 (Xi1,Yi1)单元与 (Xi2,Yi2)单元之间有一扇第 Gi 类的门,当 Gi=0 时,表示 (Xi1,Yi1) 单元与 (Xi2,Yi2) 单元之间有一面不可逾越的墙。

接下来一行,包含一个整数 S,表示迷宫中存放的钥匙的总数。

接下来 S 行,每行包含三个整数 Xi1,Yi1,Qi,表示 (Xi1,Yi1) 单元里存在一个能开启第 Qi 类门的钥匙。

输出格式

输出麦克营救到大兵瑞恩的最短时间。

如果问题无解,则输出 -1。

数据范围

|Xi1−Xi2|+|Yi1−Yi2|=1,
0GiP
1QiP
1N,M,P10
1k150

题解:这道题可以直接利用分层图最短路来做,对于钥匙可以利用1<<g来进行状态压缩,如股票状态state=1101,说明当前状态右1,3,4号钥匙。这里的边权只有0和1,如果有钥匙 ,进入下一层的图,权为0;进入其他房间,权为1,可以使用双端队列来搜索。

不过,我之前写的,一直wrong answer,最后改了好几个小时,一直找不到作物。最后发现,题目中有一句话,同一个房间可以存在多把钥匙,我也是服了,下次要注重审题。

AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<deque>
const int N=13,M=pow(2,N);
using namespace std;
typedef pair<int,int> PII;

int dor[2][N][N],key[N][N];//dor0表示竖着的门和墙,dor1表示横着的门和墙
int dis[M][N][N];//第1维表示是当前的状态层;(第2维,第3维)表示的是位置的坐标
int n,m,p;


int bfs(){
    int s=0;
    deque<PII>de;//创建双端队列
    int p[int(pow(2,N))][N][N]={0};
    dis[0][1][1]=0; //初始化(1,1)状态
    de.push_front(make_pair(0,1));//将当前状态放入双端队列,并且first代表当前状态,second是坐标的一维化((x-1)*m+y)
    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};//按照上右下左的顺序进行遍历周围的四个结点
    int ix[4]={-1,0,0,0},iy[4]={0,0,0,-1};//按照上右下左的遍历边
    while(!de.empty()){
        PII now=de.front();de.pop_front();//取出队首元素,并且删除当前队首元素
        int state=now.first,x=(now.second-1)/m+1,y=now.second-(x-1)*m;//将取出的一维坐标二维化
        if(x==n&&y==m) return dis[state][x][y];
        if(key[x][y]!=0) {
            if(dis[state|key[x][y]][x][y]>dis[state][x][y]) {
                dis[state|key[x][y]][x][y]=dis[state][x][y];
                de.push_front(make_pair(state|key[x][y],(x-1)*m+y));
            }
        }
        for(int i=0;i<4;i++){
            int nx=x+dx[i],ny=y+dy[i];
            if(nx<1||nx>n||ny<1||ny>m) continue;
            int inx=x+ix[i],iny=y+iy[i];
            int j=(i+1)%2;
            int w=dor[j][inx][iny];
            if(w==0) continue;
            if(w==-1||(state&(1<<w))>0) {//只有没有门或者有门并且有钥匙的时候才可以进入
                if(dis[state][nx][ny]>dis[state][x][y]+1){
                    dis[state][nx][ny]=dis[state][x][y]+1;
                    de.push_back(make_pair(state,(nx-1)*m+ny));
                }
            }
        }
    }
    return -1;
}

int main(){
    //初始化,-1表示此处没有钥匙,没有门墙
    memset(dor,-1,sizeof(dor));
    memset(key,0,sizeof(key));
    memset(dis,0x3f,sizeof(dis));
    cin>>n>>m>>p;
    int k;cin>>k;
    for(int i=1,x1,x2,y1,y2,g,j;i<=k;i++){
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&g);
        if(abs(x1-x2)==1) j=1;
        else j=0;
        dor[j][min(x1,x2)][min(y1,y2)]=g;
    }
    int s,x1,y1,g;cin>>s;
    while(s--){
        scanf("%d%d%d",&x1,&y1,&g);
        key[x1][y1]=max(key[x1][y1],key[x1][y1]|(1<<g));
    }
    int ans=bfs();
    cout<<ans;
    return 0;
}

写于:2020/9/9 21:58

posted @ 2020-09-09 21:58  白菜茄子  阅读(254)  评论(0编辑  收藏  举报