题目链接:https://nanti.jisuanke.com/t/T1215
题目描述
多灾多难的公主又被大魔王抓走啦!国王派遣了第一勇士蒜头君去拯救她。
身为超级厉害的术士,同时也是蒜头君的好伙伴,你决定祝他一臂之力。你为蒜头君提供了一张大魔王根据地的地图,上面标记了蒜头君和公主所在的位置,以及一些不能够踏入的禁区。你还贴心地为蒜头君制造了一些传送门,通过一个传送门可以瞬间转移到任意一个传送门,当然蒜头君也可以选择不通过传送门瞬移。传送门的位置也被标记在了地图上。此外,你还查探到公主所在的地方被设下了结界,需要集齐 K(0≤K≤5) 种宝石才能打开。当然,你在地图上也标记出了不同宝石所在的位置。
你希望蒜头君能够带着公主早日凯旋。于是在蒜头君出发之前,你还需要为蒜头君计算出他最快救出公主的时间。
地图用一个 R×C 的字符矩阵来表示。字符 S 表示蒜头君所在的位置,字符 E 表示公主所在的位置,字符 # 表示不能踏入的禁区,字符 $ 表示传送门,字符 . 表示该位置安全,数字字符 0 至 4 表示了宝石的类型。蒜头君每次可以从当前的位置走到他上下左右四个方向上的任意一个位置,但不能走出地图边界。蒜头君每走一步需要花费 1个单位时间,从一个传送门到达另一个传送门不需要花费时间。当蒜头君走到宝石所在的位置时,就视为得到了该宝石,不需要花费额外时间。
输入格式
第一行是一个正整数 T(1≤T≤10),表示一共有 T 组数据。
每一组数据的第一行包含了三个用空格分开的正整数 R、C(2≤R,C≤200)和 K,表示地图是一个R×C的矩阵,而蒜头君需要集齐 KK 种宝石才能够打开拘禁公主的结界。
接下来的 R 行描述了地图的具体内容,每一行包含了 C 个字符。字符含义如题目描述中所述。保证有且仅有一个 S 和 EE。\text{\textdollar}$ 的数量不超过 10 个。宝石的类型在数字 0 至 4 范围内,即不会超过 5 种宝石。
输出格式
对于每一组数据,输出蒜头君救出公主所花费的最少单位时间。
若蒜头君无法救出公主,则输出 "oop!"(只输出引号里面的内容,不输出引号)。
每组数据的输出结果占一行。
输出时每行末尾的多余空格,不影响答案正确性
样例输入
1 7 8 2 ........ ..S..#0. .##..1.. .0#..... ...1#... ...##E.. ...1....
样例输出
11
思路分析:
1.这题求得是最短路径,显然要用bfs;但显然一般的bfs肯定解决不了问题。
2.'S'和‘E’是bfs最基础的地方,这里就不说了
3.‘#’和‘.’也是bfs走路过程中基本元素,也不多说了
4.‘$’传送门,这是一般bfs所没有的,从一个传送门进去可以从任意一个传送门出去,所以在输入的时候肯定要把传送门坐标存起来,在广搜的时候要遍历所有的传送门。(这不算这题最难得地方,最难得还是宝石的问题)
5.‘0-4’,这是这题最坑的地方,走到终点时必须宝石拿到宝石的种类够数才算结束(注意是宝石的种类而不是宝石的数量),这题关于宝石一共有两个地方需要解决。
5.1 在遍历过程中,每个地方有可能会走多次,举个例子:假如地图中一共有两种宝石(每个只有一个),而且这两个宝石都在一个胡同里(就是只有一条路能走进去取宝石),而且两个宝石还不在一个胡同里,走到终点时要求必须拿到这两个宝石。如果按正常的思路来(二维数组标记),每个地方只能走一次,当拿到一个宝石之后,就回不来了(因为走过的路被标记了,无法回来了),这样肯定解决不了这个问题了,所以就要用的三维数组标记来解决问题了。这里面一维和二维就是横坐标和纵坐标,三维用来存宝石的状态,按上面的例子来说,当一路走过去拿第一个宝石是,走的路肯定被标记了,但是标记的是没拿宝石路径,等拿到宝石之后,这时候状态又不一样了,这时候就能原路返回了(三维数组)。
当然解决这个问题,这题还没结束,还好解决宝石种类的问题,如果看第二个宝石问题之前必须要把这个三维数组的方法学会,这里有一个用bfs+三位数组就能解决的例题 点击这里
5.2 第二个要解决的问题就是宝石的种类,这里用到了二进制状态压缩,如果不用这个方法能解决这个问题的话,也可以不用。如果不知道啥的二进制压缩,可以先看看这个 点击这里
这里面一共有五种宝石0-4,所以如果用二进制五位二进制就可以解决问题,11111代表有5种宝石,00000代表一种宝石都没有(1的数量代表宝石种类的数量),在广搜中,如果搜到了终点,写一个函数,判断有几种宝石,如种类的数量够了,就可以输出答案了。
三维标记数组book [ tx ] [ ty ] [ tk ] 代表的意思是在坐标(tx,ty)处拥有宝石种类的数量为tk,如果五种宝石都有就是11111(二进制),换算成十进制就是32,所以在定义数组是第三位开的只要大于32就够用了,至于具体怎样用二进制状态压缩宝石种类的状态的,可以看代码,如果关于二进制压缩的代码看不懂,可以看看这里 点击这里
代码:
#include<iostream> #include<cstring> #include<queue> using namespace std; char a[201][201];//存储输入坐标 int nex[4][2]={0,-1,-1,0,0,1,1,0};//右下左上 int s[10][2];//记录传送门 int r,c,k; int book[202][202][35];//标记数组(横坐标,纵坐标,当前坐标宝石种类的数量) int t,sum;//分别记录传送门和宝石的数量 struct dd { int x; int y; int step;//步数 int key;//宝石种类的数量 dd(int xx,int yy,int ss,int kk):x(xx),y(yy),step(ss),key(kk){}; }; int check(int tk)//检查宝石的数量够不够 { int l=0; for(int i=0;i<5;i++) if((tk>>i)&1)//位运算 l++; if(l>=k) return 1; else return 0; } int bfs(int x,int y) { queue<dd> q; book[x][y][0]=1; dd temp(x,y,0,0); q.push(temp); while(q.empty()==0)//队列不空 { temp=q.front(); q.pop(); for(int i=0;i<4;i++) { int tx=temp.x+nex[i][0]; int ty=temp.y+nex[i][1]; int tt=temp.step; int tk=temp.key; if(tx<0 || tx>=r || ty<0 || ty>=c) continue; if(book[tx][ty][tk]==1) continue; if(a[tx][ty]=='#') continue; if(a[tx][ty]=='E')//终点 { if(check(tk)==1) return temp.step+1; } if(a[tx][ty]>='0'&&a[tx][ty]<='9')//数字 { book[tx][ty][tk]=1; tk=tk|(1<<(a[tx][ty]-'0'));//重点 q.push(dd(tx,ty,tt+1,tk)); } if(a[tx][ty]=='.') { book[tx][ty][tk]=1; q.push(dd(tx,ty,tt+1,tk)); } if(a[tx][ty]=='$')//传送门 { book[tx][ty][tk]=1; q.push(dd(tx,ty,tt+1,tk));//传送门还可以不传送 for(int j=0;j<t;j++) { tx=s[j][0]; ty=s[j][1]; if(book[tx][ty][tk]==0)//如果tx、ty变换,一定要记得重新判断book { book[tx][ty][tk]=1; q.push(dd(tx,ty,tt+1,tk)); } } } } } return -1; } int main() { int T,tx,ty; cin>>T; while(T--) { sum=0;//宝石置零 t=0;//传送门置零 memset(book,0,sizeof(book)); cin>>r>>c>>k; for(int i=0;i<r;i++) { for(int j=0;j<c;j++) { cin>>a[i][j]; if(a[i][j]=='S') { tx=i; ty=j; a[i][j]='.'; } if(a[i][j]=='$') { s[t][0]=i; s[t][1]=j; t++;//传送门总数量 } if(a[i][j]>='0'&&a[i][j]<='9') sum++; } } if(k>sum)//优化 { cout<<"oop!"<<endl; continue; } int ans=bfs(tx,ty); if(ans==-1) cout<<"oop!"<<endl; else cout<<ans<<endl; } return 0; }
注:在搜索时,等计算出来tx,ty后,第一个条件就要是判断坐标是否越界了,不要先判断这个坐标是否走过,不然会造成段错误。
上面代码中,在结构里面有一个 dd(int xx,int yy,int ss,int kk):x(xx),y(yy),step(ss),key(kk){}; 这个作用是把符合条件的坐标入队用的。代码中其他的地方应该都好理解一些