一道码量比较大的套路广搜题……

首先看数据 \(n,m \leq 1000\) 不是特别恶心,不难想到广搜。

由于这道题每走一步的代价不是 \(1\),所以并不能保证每次搜到的点就是最优解。那么可以考虑用一个数组 \(h[i][j][sun][kill]\) 来维护一下。

其中的 \({i,j,sun,kill}\) 是记录一个状态,表示在走到点 \((i,j)\) 时是否有太阳花田和楼关剑。而 \(h[i][j][sun][kill]\) 则表示到达这个状态下的最短路径。

这样就可以有效的剪枝了,因为有了最短路数组的维护我们可以排除到达当前状态没有最短路优的路径。而如果比最短路优,那么最短路就更新为当前路径,效率将有所提高。

期望得分:\(80-90\)

接下来考虑用优先队列优化。

可以这样想,我们把所有广搜的状态存入优先队列,每次弹出当前最短的状态,可以证明其扩展到的点一定也是最短的(因为没有负环)。

于是重载一下小于号运算符就行了,搜到终点就一定是最短路,然后把状态 \((i,j,sun,kill)\) 打上标记即可。其实和 \(\operatorname{dijkstra}\) 方法类似。

时间复杂度 \(O(4nm\) \(log\) \(4nm)\)

\(\operatorname{Coding}\) 时注意两点:

  • 传送门前后都要判一下标记

  • 楼观剑可以经过而不取

#include<bits/stdc++.h>
using namespace std;
const int max_n=1000+5;
int a[max_n][max_n];
bool h[max_n][max_n][2][2];//标记状态
struct node{
int x,y,f,sun,kill;
bool operator<(const node&a)const{//重载小于运算符
return f>a.f;
}
};
priority_queue<node>q;
vector< pair<int,int> >s;//传送门
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//上下左右
int main(){
ios::sync_with_stdio(false);
int n,m;cin>>n>>m;
int fx,fy,lx,ly;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
char ch;cin>>ch;
if(ch=='S')fx=i,fy=j;
if(ch=='E')lx=i,ly=j;
if(isdigit(ch))a[i][j]=ch-'0';
if(ch=='X')a[i][j]=6,s.push_back(make_pair(i,j));
}
}
h[fx][fy][0][0]=1;
q.push({fx,fy,0,0,0});
while(!q.empty()){
int x=q.top().x,y=q.top().y,f=q.top().f;
int sun=q.top().sun,kill=q.top().kill;
if(x==lx&&y==ly){//到终点一定是最优解
cout<<f<<"\n";
return 0;
}
for(int i=0;i<4;i++){
int tx=x+dir[i][0],ty=y+dir[i][1];
if(!(tx>=1&&tx<=n&&ty>=1&&ty<=m))continue;//不能超边界
if(a[tx][ty]==6){//传送门
if(h[tx][ty][sun][kill])continue;//这里不写84分AC再见
for(int j=0;j<s.size();j++){//枚举下一个传送门
int ttx=s[j].first,tty=s[j].second;
if(tx==ttx&&ty==tty)continue;//传送门当然不能自己传给自己哦
if(h[ttx][tty][sun][kill])continue;
h[ttx][tty][sun][kill]=1;//标记
q.push({ttx,tty,f+2,sun,kill});//入队
}
}
if(a[tx][ty]==0){//普通的点
if(h[tx][ty][sun][kill])continue;
h[tx][ty][sun][kill]=1;
q.push({tx,ty,f+1,sun,kill});
}
if(a[tx][ty]==1&&kill){//这里是墙,要经过必须要有楼观剑
if(h[tx][ty][sun][kill])continue;
h[tx][ty][sun][kill]=1;
q.push({tx,ty,f+1,sun,kill});
}
if(a[tx][ty]==2){//小怪兽
if(h[tx][ty][sun][kill])continue;
h[tx][ty][sun][kill]=1;
if(kill||sun)q.push({tx,ty,f+1,sun,kill});//如果有楼观剑或太阳花就可以直接上去
else q.push({tx,ty,f+4,sun,kill});//否则要花时间把它打死
}
if(a[tx][ty]==3){//大怪兽(和小怪兽同理)
if(h[tx][ty][sun][kill])continue;
h[tx][ty][sun][kill]=1;
if(kill||sun)q.push({tx,ty,f+1,sun,kill});
else q.push({tx,ty,f+9,sun,kill});
}
if(a[tx][ty]==4){//太阳花,只要经过就能得到
if(h[tx][ty][1][kill])continue;
h[tx][ty][1][kill]=1;
q.push({tx,ty,f+1,1,kill});
}
if(a[tx][ty]==5){//楼观剑
if(h[tx][ty][sun][1]==0){//取
h[tx][ty][sun][1]=1;
q.push({tx,ty,f+6,sun,1});
}
if(h[tx][ty][sun][kill]==0){//不取
h[tx][ty][sun][kill]=1;
q.push({tx,ty,f+1,sun,kill});
}
}
}
q.pop();
}
cout<<"We want to live in the TouHou World forever\n";
return 0;
}

\(\operatorname{Update}\) \(\operatorname{On}\) \(\operatorname{2019.08.26}\)

05-11 23:01