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:当 Gi≥1 时,表示 (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,
0≤Gi≤P
1≤Qi≤P
1≤N,M,P≤10
1≤k≤150
题解:这道题可以直接利用分层图最短路来做,对于钥匙可以利用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
作者:孙建钊
出处:http://www.cnblogs.com/sunjianzhao/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。