c++ 孤岛营救问题
孤岛营救问题
题目描述
1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图。迷宫的外形是一个长方形,其南北方向被划分为N 行,东西方向被划分为M列,于是整个迷宫被划分为 N×M 个单元。每一个单元的位置可用一个有序数对(单元的行号,单元的列号)来表示。南北或东西方向相邻的 2 个单元之间可能互通,也可能有一扇锁着的门,或者是一堵不可逾越的墙。迷宫中有一些单元存放着钥匙,并且所有的门被分成 P类,打开同一类的门的钥匙相同,不同类门的钥匙不同。
大兵瑞恩被关押在迷宫的东南角,即(N,M)单元里,并已经昏迷。迷宫只有一个入口,在西北角。也就是说,麦克可以直接进入(1,1)单元。另外,麦克从一个单元移动到另一个相邻单元的时间为1,拿取所在单元的钥匙的时间以及用钥匙开门的时间可忽略不计。
试设计一个算法,帮助麦克以最快的方式到达瑞恩所在单元,营救大兵瑞恩。
输入
第 1行有 3个整数,分别表示N,M,P的值。[均小于等于10]
第 2 行是1个整数 K[小于等于150],表示迷宫中门和墙的总数。
第 I+2 行(1<=I<=K) ,有 5 个整数,依次为Xi1,Yi1,Xi2,Yi2,Gi: 当Gi>=1时,表示(Xi1,Yi1)单元与(Xi2,Yi2)单元之间有一扇第Gi类的门,当 Gi=0时,表示(Xi1,Yi1)单元与(Xi2,Yi2)单元之间有一堵不可逾越的墙(其中,|Xi1-Xi2|+|Yi1-Yi2|=1,0<=Gi<=P) 。
第K+3行是一个整数S,表示迷宫中存放的钥匙总数。
第K+3+J 行(1<=J<=S),有3个整数, 依次为Xi1,Yi1,Qi: 表示第J 把钥匙存放在(Xi1,Yi1)单元里,并且第J 把钥匙是用来开启第Qi类门的。 (其中 1<=Qi<=P) 。
输入数据中同一行各相邻整数之间用一个空格分隔。
输出
程序运行结束时,将麦克营救到大兵瑞恩的最短时间的值输出。如果问题无解,则输出-1。
样例输入
4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2
4 2 1
样例输出
14
提示
存在一个格子有多把钥匙的情况
AC代码
#include <stdio.h>//includes
#include <string.h>//
int n,m,p,k;//定义变量
int d[11][11][11][11];//d[][][[][]存储障碍物的类型
int kn,g[11][11][100],gn[11][11];//kn钥匙的总数 g[][][]是此位置上第几把钥匙是开哪扇门的钥匙
int dx[4]={0,0,1,-1};//dxdy上下左右扩展
int dy[4]={1,-1,0,0};//up
struct point//结构体
{
char x,y;//x坐标和y坐标
int step;//步数
bool key[11];//当前有没有这把钥匙
};
int fun(point u)//计算钥匙的类型 比如一把2类型的钥匙返回2 没有钥匙返回0 有一把2类型和一把1类型的钥匙返回3(2 + 1)
{
int ans = 0,p = 1;
for (int i = 1;i <= 10;i ++)
{
ans += p * u.key[i];
p = p * 2;
}
return ans;
}
point q[650000],s;//定义数组,变量
bool used[11][11][1024];//up
int f,e;//up
int main()//main()
{
scanf("%d %d %d",&n,&m,&p);//输入n行,m列,p种门
scanf("%d",&k);//输入k(障碍物的总数)
memset(used,0,sizeof(used));//初始化used[][][]和g[][][]为0
memset(g,0,sizeof(g));//up
for(int i=1;i<=k;i++)//输入整个孤岛的具体情况
{
int a1,b1,a2,b2;//a1b1是第一个xy坐标 a2b2是第二个xy坐标
scanf("%d%d%d%d",&a1,&b1,&a2,&b2);//输入他们
scanf("%d",&d[a1][b1][a2][b2]);//输入这个障碍物的类型
if(d[a1][b1][a2][b2]==0)//如果这是一堵不可逾越的墙
d[a1][b1][a2][b2]=-1;//那么,就把他标成-1 后面判断条件时要用到
d[a2][b2][a1][b1]=d[a1][b1][a2][b2];//重复赋值 (比如 坐标(1,2) 和 坐标(1,3) 之间有障碍物 那么 坐标(1,3) 和 坐标(1,2) 之间就一定有一个障碍物)
}
scanf("%d",&kn);//钥匙的总数
memset(gn,0,sizeof(gn));//钥匙的数量
for(int i=1;i<=kn;i++)//捡起钥匙
{
int a1,b1;//钥匙所在的具体位置
scanf("%d%d",&a1,&b1);//输入他们
gn[a1][b1]++;//钥匙数量+1
scanf("%d",&g[a1][b1][gn[a1][b1]]);//输入钥匙
//如果这个点上有1把钥匙 就输入到g[x][y][1];
//如果这个点上有2把钥匙 就输入到g[x][y][2];
//g[x][y][1] = 2就代表这里有开类型2门的钥匙
}
s.x=1;s.y=1;s.step=0;//初始化
memset(s.key,0,sizeof(s.key));//把钥匙的状态初始化
q[1]=s;//把第一个点入队
int f=1,e=1;//f出队 e入队
while(f<=e)//这里开始bfs,不注释了
{
point u=q[f++];
for(int i=0;i<4;i++)
{
point v=u;
v.x=u.x+dx[i];
v.y=u.y+dy[i];
v.step=u.step+1;
memcpy(v.key,u.key,sizeof(u.key));//把u.key 复制到 v.key
if(v.x<1||v.x>n||v.y<1||v.y>m)continue;//如果此位置不在孤岛上
if(d[u.x][u.y][v.x][v.y]==-1)continue;//如果这是一堵不可逾越的墙
if (used[v.x][v.y][fun(v)] == 1) continue;//如果这个位置曾经走过 used[4][2][3] = 1 (两把钥匙的情况)代表(4,2)这个位置有一把2类型和一把1类型的钥匙
//并且,该位置已走过 used[2][3][2] = 1(一把钥匙的情况)代表这个位置上有一把2类型的钥匙
if(d[u.x][u.y][v.x][v.y]>0)//如果这里是门
{
if(u.key[d[u.x][u.y][v.x][v.y]]==0)continue;//而且没有钥匙
}
/*
(此处少一个门的类型与钥匙的类型对应的逻辑?)
if (d[][][][] != g[][][])
continue;
*/
if(gn[v.x][v.y]>0)//gn:此位置上是否有钥匙, (有几把钥匙)
{
for(int j=1;j<=gn[v.x][v.y];j++)
v.key[g[v.x][v.y][j]]=1;//g[v.x][v.y][j]:此位置上钥匙的类型
//v.key[2] = 1:这个点上有开类型2门的钥匙
}
if(v.x==n&&v.y==m)
{
printf("%d\n",v.step);
return 0;
}
e ++;
q[e] = v;
used[v.x][v.y][fun(v)] = 1;
}
}
printf("-1\n");//如果没有退出的话,就是没找到,就输出"-1"
return 0;//退出
}