一道码量比较大的广搜题,但让我这个辣鸡小学生自闭了一天呜呜呜。
一开始看数据\(n,m \leq 1000\)也并不是特别大,于是用就开始用广搜乱水了。
由于这道题每走一步的代价不是\(1\),所以并不能保证每次广搜搜到的点就是最优解。所以我考虑用一个数组来\(h[i][j][sun][kill]\)维护一下。
其中的\({i,j,sun,kill}\)是记录一个状态,表示在走到点\((i,j)\)时是否有太阳花田和楼关剑。而\(h[i][j][sun][kill]\)则表示到达这个状态下的最短路径。
这样就可以有效的剪枝了,因为有了最短路数组的维护我们可以排除到达当前状态没有最短路优的路径。而如果比最短路优,那么最短路就更新为当前路径,效率将大大提高。
最坏时间复杂度\(O(4nm \times\) 玄学 \()\)
期望得分:\(90-100\)
实际得分\(72\)
这是我一开始自闭的真实写照。两个点\(TLE\)一个点\(WA\),开了氧气优化。
\(dalao\)们告诉我要先把\(WA\)的那个点搞定,于是我冥思苦想检查代码,结果最后发现楼观剑也可以经过而不取。
赶紧码上这种情况\(qwq\)
期望的分:\(84\)
实际得分:\(64\)
直觉告诉我可能是多了一种情况空间或者时间爆炸了。
我\(TM\)的真想。
瞄了一眼题解,发现广搜其实还能用优先队列搞。
可以这样想,我们把所有广搜的状态存入优先队列,每次弹出当前最短的状态,可以证明其扩展到的点一定也是最短的(因为没有负环)。
于是重载一下小于号运算符就行了,搜到终点就一定是最短路,然后把到的状态\((i,j,sun,kill)\)打上标记即可。
最坏时间复杂度\(O(4nm\) \(log\) \(4nm)\)
期望得分\(100\)
实际得分\(84\)
想不通啊,数据就算把我卡到最坏开\(O3\)竟然也过不了 \(?\)
在讨论区里求助被无视了,我\(TM\)的是个小学生哎,这样对我。。。
于是就很自闭地想了一晚上。
最后发现原来是传送门出的锅\(qwq\)
假如说我们从一个传送门进,一个传送门出,两边的状态是不是都要判断一下以前是否走过呢 \(?\)
而蒟蒻的我就是因为只判了一个,导致如果传送门很多就会让我的程序咕咕咕了。。
\(Code:\)
#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;
}