题目链接
多灾多难的公主又被大魔王抓走啦!国王派遣了第一勇士蒜头君去拯救她。身为超级厉害的术士,同时也是蒜头君的好伙伴,你决定祝他一臂之力。你为蒜头君提供了一张大魔王根据地的地图,上面标记了蒜头君和公主所在的位置,以及一些不能够踏入的禁区。你还贴心地为蒜头君制造了一些传送门,通过一个传送门可以瞬间转移到任意一个传送门,当然蒜头君也可以选择不通过传送门瞬移。传送门的位置也被标记在了地图上。此外,你还查探到公主所在的地方被设下了结界,需要集齐 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的矩阵,而蒜头君需要集齐 KKK 种宝石才能够打开拘禁公主的结界。 接下来的 R 行描述了地图的具体内容,每一行包含了 C 个字符。字符含义如题目描述中所述。保证有且仅有一个 S 和 E。$ 的数量不超过 10 个。宝石的类型在数字 0 至 4 范围内,即不会超过 5 种宝石。
输出格式对于每一组数据,输出蒜头君救出公主所花费的最少单位时间。若蒜头君无法救出公主,则输出 “oop!”(只输出引号里面的内容,不输出引号)。每组数据的输出结果占一行。
样例输入
1
7 8 2
........
..S..#0.
.##..1..
.0#.....
...1#...
...##E..
...1....
样例输出
11
这个题与传统的bfs的题相比多了一个收集宝石和传送门,一开始看到传送门很头大,后来我用记录传送门坐标的方法,一旦在广搜过程中碰到了传送门,就直接把未访问过的传送门都记录在队列里就可以了。
解决了传送门我们来想想怎么记录走到的位置拥有的宝石,根据题意,宝石最多有五种,每个宝石用不同的数字代替,要收集够k种宝石才能拯救公主,为了防止在拥有不同宝石情况时访问同一点无法访问,比如我现在拥有1、3号宝石我想访问这个点,访问结束我们二维数组标记了这个点,下一个拥有1、2号宝石的其他点就无法访问这个点了,为了解决这个问题我们可以用三维数组标记一个点,前两维记录坐标,第三维记录此时的宝石情况。如果你从没使用过三维数组,我觉得你可以先尝试下这个题,点击这里。
想到这里我不禁感叹,我可以用1来代表拥有0号宝石,11代表拥有0号和1号宝石,111代表拥有0号和1号和2号宝石,那么同理1001就是拥有0号宝石和3号宝石了,然后我开心的提交了下面的代码:
//拯救公主-内存超限写法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
struct muban
{
int x;
int y;
int num;
int time;
}que[45000];
char imap[205][205];
int book[205][205][11114];
int s[11][2];
int inext[4][2]={
{
0,1},{
1,0},{
0,-1},{
-1,0}};
int ichange(int x,char shu)
{
int q=shu-'0';
int num=1;
while(q--)
num*=10;
x+=num;
return x;
}
int judge(int x,char shu)
{
int q=shu-'0';
while(q--)
{
x=x/10;
}
x=x%10;
if(x==1)
return 1;
else if(x==0)
return 0;
}
int main()
{
int t;
cin>>t;
while(t--)
{
memset(book,0,sizeof(book));
int n,m,k,num_s;
cin>>n>>m>>k;
int stax,stay,endx,endy;
num_s=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cin>>imap[i][j];
if(imap[i][j]=='S')
{
stax=i;
stay=j;
}
if(imap[i][j]=='$')
{
s[num_s][0]=i;
s[num_s][1]=j;
num_s++;
}
}
}
int bao_num=1;
if(k==0)
bao_num=0;
else
{
for(int i=1;i<k;i++)
bao_num=bao_num*10+1;
}
int tx,ty,tn,tt,min_time,head,tail;
head=1;
tail=2;
que[head].x=stax;
que[head].y=stay;
que[head].num=0;
que[head].time=0;
book[stax][stay][0]=1;
min_time=40005;
while(head<tail)
{
for(int i=0;i<4;i++)
{
tx=que[head].x+inext[i][0];
ty=que[head].y+inext[i][1];
tn=que[head].num;
tt=que[head].time;
if(tx>=0&&tx<n&&ty>=0&&ty<m)
{
if(book[tx][ty][tn]==0&&imap[tx][ty]=='.')
{
que[tail].x=tx;que[tail].y=ty;
que[tail].time=tt+1;
que[tail].num=tn;
book[tx][ty][tn]=1;
tail++;
}
else if(book[tx][ty][tn]==0&&imap[tx][ty]>='0'&&imap[tx][ty]<='4')
{
que[tail].x=tx;que[tail].y=ty;
que[tail].time=tt+1;
book[tx][ty][tn]=1;
if(judge(que[head].num,imap[tx][ty])==0)//如果这个宝石没有捡起
que[tail].num=ichange(que[head].num,imap[tx][ty]);
else
que[tail].num=que[head].num;
tail++;
}
else if(book[tx][ty][tn]==0&&imap[tx][ty]=='$')
{
que[tail].x=tx;que[tail].y=ty;que[tail].num=tn;
que[tail].time=tt+1;
book[tx][ty][tn]=1;
tail++;
for(int j=0;j<num_s;j++)
{
if(book[s[j][0]][s[j][1]][tn]==0)//对于每一个传送门要再进行判断
{
que[tail].x=s[j][0];
que[tail].y=s[j][1];
que[tail].time=tt+1;
que[tail].num=tn;
book[s[j][0]][s[j][1]][tn]=1;
tail++;
}
}
}
else if(book[tx][ty][tn]==0&&imap[tx][ty]=='E'&&que[head].num==bao_num)
{
if(que[head].time+1<min_time)
min_time=que[head].time+1;
}
}
}
head++;
}
if(min_time==40005)
cout<<"oop!"<<endl;
else
cout<<min_time<<endl;
}
return 0;
}
得到的结果是 Memory Limit Exceeded,并且使用内存高到了1825372kB,而题目要求的内存限制是65536KB,所以我们要使用二进制压缩的方法记录此时的宝石个数,三维只需要开到35就可以了。
其中涉及到的tn=tn|(1<<k)这行代码的意思是使tn的第k位数字为1,(x>>i)&1代码的意思是取出x的二进制中第i位数字。
AC代码如下:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
struct muban
{
int x;
int y;
int num;
int time;
}que[95000];
char imap[205][205];
int book[205][205][35];
int s[11][2];
int inext[4][2]={
{
0,1},{
1,0},{
0,-1},{
-1,0}};
int n,m,k,qq,num_s;
int judge(int x)
{
int num=0;
for(int i=0;i<5;i++)
{
if((x>>i)&1)
num++;
}
if(num>=k)
return 1;
else
return 0;
}
int main()
{
int t;
cin>>t;
while(t--)
{
memset(book,0,sizeof(book));
qq=0;//记录宝石个数
num_s=0;//记录传送门个数
cin>>n>>m>>k;
int stax,stay,endx,endy;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cin>>imap[i][j];
if(imap[i][j]=='S')
{
stax=i;
stay=j;
}
if(imap[i][j]=='$')
{
s[num_s][0]=i;
s[num_s][1]=j;
num_s++;
}
if(imap[i][j]>='0'&&imap[i][j]<='9')
qq++;
}
}
if(k>qq)
{
cout<<"oop!"<<endl;
continue;
}
int tx,ty,tn,tt,min_time,head,tail;
head=1;
tail=2;
que[head].x=stax;
que[head].y=stay;
que[head].num=0;
que[head].time=0;
book[stax][stay][0]=1;
min_time=40005;
while(head<tail)
{
for(int i=0;i<4;i++)
{
tx=que[head].x+inext[i][0];
ty=que[head].y+inext[i][1];
tn=que[head].num;
tt=que[head].time;
if(tx>=0&&tx<n&&ty>=0&&ty<m)
{
if(book[tx][ty][tn]==0&&imap[tx][ty]=='.')
{
que[tail].x=tx;que[tail].y=ty;
que[tail].time=tt+1;
que[tail].num=tn;
book[tx][ty][tn]=1;
tail++;
}
else if(book[tx][ty][tn]==0&&imap[tx][ty]>='0'&&imap[tx][ty]<='4')
{
que[tail].x=tx;que[tail].y=ty;
que[tail].time=tt+1;
book[tx][ty][tn]=1;
que[tail].num=tn|(1<<(imap[tx][ty]-'0'));
tail++;
}
else if(book[tx][ty][tn]==0&&imap[tx][ty]=='$')
{
que[tail].x=tx;que[tail].y=ty;que[tail].num=tn;
que[tail].time=tt+1;
book[tx][ty][tn]=1;
tail++;
for(int j=0;j<num_s;j++)
{
if(book[s[j][0]][s[j][1]][tn]==0)//对于每一个传送门要再进行判断
{
que[tail].x=s[j][0];
que[tail].y=s[j][1];
que[tail].time=tt+1;
que[tail].num=tn;
book[s[j][0]][s[j][1]][tn]=1;
tail++;
}
}
}
else if(book[tx][ty][tn]==0&&imap[tx][ty]=='E'&&judge(tn)==1)
{
if(que[head].time+1<min_time)
min_time=que[head].time+1;
}
}
}
head++;
}
if(min_time==40005)
cout<<"oop!"<<endl;
else
cout<<min_time<<endl;
}
return 0;
}