[BalticOI 2011]Switch the Lamp On
题目
解说
先从建图说起。
显然如果一个格子内符号为 \ ,那么我们不需要转动元件就可以从左上角走到右下角,即从左上角走到右下角走了一条边权为\(0\)的边,而如果想从左下角走到右上角则需要转动一次元件,也可以抽象为从左上角走到右下角走了一条边权为\(1\)的边。符号为 / 时与之同理。
图建好之后我们就可以直接跑最短路了……吗?
如果我们放纵一下自己开\(O2\)的话确实可以轻松水过,但是这并不是我们希望看到的结果。
下面展示一下硬跑迪杰斯特拉和\(SPFA\)的优秀战果。
迪杰斯特拉:\(TLE\) \(88\)分
SPFA:\(TLE\) \(86\)分
这不尴尬了吗[托腮] [托腮]
显然我们需要进行某些改良才行。不难发现在这道题中边权只有\(0\)和\(1\)两种,我们应该充分利用这一特点。显然我们应该尽可能走边权为\(0\)的边,这时候我们想起\(SPFA\)中有一种叫双端队列优化的东西,那么我们只需将其稍作修改,通往目前点的边权若为\(0\)则将其放在前端,否则放在后端,这样就保证了每个点的进队数量尽量少,并且可以保证第一次搜到终点时其答案就是最小的,可以直接退出。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=500+3;
int head[maxn*maxn],tot,n,m,dis[maxn*maxn];
struct edge{
int to,next,w;
}e[maxn*maxn*4];//一定注意边的数量!!!
void add(int a,int b,int w){
e[++tot].to=b;
e[tot].w=w;
e[tot].next=head[a];
head[a]=tot;
}
void spfa(){
deque<int> q;
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push_front(1);
while(!q.empty()){
int u=q.front();
q.pop_front();
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(dis[u]+e[i].w<dis[v]){
dis[v]=dis[u]+e[i].w;
if(v==(n+1)*(m+1)) return;
if(!e[i].w) q.push_front(v);
else q.push_back(v);
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
char tmp;
scanf(" %c",&tmp);
if(tmp=='/'){
add((i-1)*(m+1)+j+1,i*(m+1)+j,0);
add(i*(m+1)+j,(i-1)*(m+1)+j+1,0);
add((i-1)*(m+1)+j,i*(m+1)+j+1,1);
add(i*(m+1)+j+1,(i-1)*(m+1)+j,1);
}
else{
add((i-1)*(m+1)+j+1,i*(m+1)+j,1);
add(i*(m+1)+j,(i-1)*(m+1)+j+1,1);
add((i-1)*(m+1)+j,i*(m+1)+j+1,0);
add(i*(m+1)+j+1,(i-1)*(m+1)+j,0);
}
}
}
spfa();
if(dis[(n+1)*(m+1)]==0x3f3f3f3f) printf("NO SOLUTION\n");
else printf("%d\n",dis[(n+1)*(m+1)]);
return 0;
}
幸甚至哉,歌以咏志。
签名:
我将轻轻叹息,叙述这一切,
许多许多年以后:
林子里有两条路,我——
选择了行人稀少的那一条,
它改变了我的一生。