题目链接:https://ac.nowcoder.com/acm/contest/318/H
题目大意:从起点S开始走,空载时每步消耗时间为1,我们现在要拿着XYZ三瓶可乐到E处,然后每个可乐有它特定的原力值(就是消耗的时间为所拿可乐中原力值的最大值再加上1),问我们要怎么走以什么顺序拿可乐到达终点的时间消耗最少(可以走重复的点)。并且,当所拿可乐小于3瓶时可以翻墙。
【【【用到了状态压缩,状态压缩第一题】】】(关键点就是二进制位运算 理解了挺久的 呜呜呜)
题解:因为可以重复走相同的点,所以我们如何来找最短时间?相同的点可以重复走,但是相同的点相同的状态(相同的状态就是携带的可乐的情况)是不能重复走的,不然就会浪费时间,也就是时间不是最优。所以这里我们用状态压缩来表示可乐是否拿了,然后又用了优先队列用来得到当前最小时间。我们要把当前点周围的四个点所有可行的状态push进队,写的时候就是要仔细一点想好所有的情况就好了。我的分类是以下:
先看是否在图中,然后看是不是拿满了可乐但是将要走的点是墙,如果是就continue;
再看将要走到E,即终点,这个要分为两种:1.已经拿满了可乐,这时就已经是要到最后一步了就直接return;2.如果没有拿满可乐,就看是否走过,没走过就push进队
然后再看走到有可乐的点,即XYZ点,这个时候有三种情况:1.如果当前点的可乐已经被拿了,那就当作普通点来看 2.如果没有被拿,又可以分为两种 (1)把可乐拿上 ;(2)不拿可乐,也就是当普通点看待
最后就只剩下普通点了,就直接看当前状态是不是走过,没走过的话就进队,并且走掉
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <map>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
int n,m;//n*m地图
char mp[105][105];
int a[5];//可乐的原力值(也就是结构体里的speed,影响速度),a[0]是X的,a[1]是Y的,a[2]是Z的
int sx,sy;//起点的位置
bool vis[105][105][10];//每个点都有八种可走状态(1<<0是拿了“0”可乐,也就是X;1<<1是拿了“1”可乐,也就是Y;1<<2是拿了“2”可乐,也就是Z)(000是空载,001是拿了X,011是拿了X和Y,111是拿了X,Y,Z,以此类推一共八种状态)
const int dir[4][2]={
1,0,
0,1,
-1,0,
0,-1
};
struct node{
int x,y,state,speed,t;//这里的x,y是坐标,state是当前拿了多少可乐的状态,speed是当前的影响速度,t是走到该点所用的时间
node(int a=0,int b=0,int c=0,int d=0,int e=0):x(a),y(b),state(c),speed(d),t(e){}
friend bool operator <(node n1,node n2)
{
return n1.t>n2.t;//这里我们按照时间排序,所用时间小的先出队列,因为所有可走的点我们都要push进队列,进队之后自动按照时间排序,最终到达终点并且可行的方案就一定是最快的
}
};
bool InMap(int x,int y)
{
return x>=1&&y>=1&&x<=n&&y<=m;
}
int bfs()
{
memset(vis,false,sizeof(vis));
priority_queue<node>q;
q.push(node(sx,sy,0,0,0));
vis[q.top().x][q.top().y][0]=true;//也就是起点的空载状态(000)已经走过
while(!q.empty())
{
for(int i=0;i<4;i++)
{
int tx=q.top().x+dir[i][0];
int ty=q.top().y+dir[i][1];
if(!InMap(tx,ty)||(q.top().state==7&&mp[tx][ty]=='#'))//如果将要走的点不在地图中或者此刻已经拿满了可乐但是下一步是墙,就没法走
continue;
if(mp[tx][ty]=='E')//将要走到终点了
{
//如果当前拿满了可乐
if(q.top().state==7)
return q.top().t+q.top().speed+1;
//否则,未拿满可乐
if(!vis[tx][ty][q.top().state])//如果没有走过
{
q.push(node(tx,ty,q.top().state,q.top().speed,q.top().t+q.top().speed+1));
vis[tx][ty][q.top().state]=true;
}
}
else if(mp[tx][ty]>='X'&&mp[tx][ty]<='Z')//走到有可乐的点
{
int id=mp[tx][ty]-'X';//这里记录的是 当前点是哪个可乐(0 1 2)
if(q.top().state>>id&1)//这里就是看将要走的点的可乐有没有已经被拿,如果已经拿了就还是可以走
{
if(!vis[tx][ty][q.top().state])
{
q.push(node(tx,ty,q.top().state,q.top().speed,q.top().t+q.top().speed+1));
vis[tx][ty][q.top().state]=true;
}
}
//否则就是当前点的可乐没有被拿
else
{
if(!vis[tx][ty][q.top().state])//我们不拿这个点的可乐
{
q.push(node(tx, ty, q.top().state, q.top().speed, q.top().t + q.top().speed + 1));
vis[tx][ty][q.top().state] = true;
}
//我们拿上这个点的可乐
int state=q.top().state|1<<id;//状态标记为拿上该点的可乐
int speed=max(q.top().speed,a[id]);//标记影响速度为大的一个
q.push(node(tx,ty,state,speed,q.top().t+q.top().speed+1));
vis[tx][ty][state]=true;
}
}
else//剩下的就是普通点
{
if(!vis[tx][ty][q.top().state])
{
q.push(node(tx, ty, q.top().state, q.top().speed, q.top().t + q.top().speed + 1));
vis[tx][ty][q.top().state] = true;
}
}
}
q.pop();//将队首周围点所有可以的状态点都push进了队列,就pop掉
}
return 0;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
getchar();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%c",&mp[i][j]);
if(mp[i][j]=='S')//将起点记录下来,搜的时候将这个点当作普通点看待
{
sx=i;
sy=j;
mp[i][j]='.';
}
}
getchar();
}
for(int i=0;i<3;i++)
scanf("%d",&a[i]);
int ans=bfs();
printf("%d\n",ans);
}
return 0;
}