题解 P3831 [SHOI2012]回家的路
什么叫分层图最短路,我不会/kk
感觉自己做法和其他题解不大一样所以过来发篇题解了。
题目大意
就是说给你一个 $n \times n$ 的网格图和 $m$ 个可换乘点,然后你只能在同一行或同一列(如果在行上移动,就不能在列上移动;反之同理)上移动,除非这个点是可以换乘的。每次走一格花费 $2$ 费,换乘花费 $1$ 费,求从 $(sx,sy)$ 到 $(fx,fy)$ 的最小费用。
其中:$n \leqslant 20000,m \leqslant 100000$
分析
可以发现如果对于每个格点都存储信息是不可行的,我们考虑优化空间:发现我们的路径实际上就是一直冲,然后只有在部分换乘点转弯,所以我们可以把换乘点联系起来,在换乘点之间跑最短路。
具体做法
那么具体怎么做呢,我们考虑对于每行和每列开领接表,存储这一行或这一列上,存在的换乘点的编号。然后可以把起始点也看做换乘点。然后跑最短路的时候就直接通过两点的距离$\times 2 + 1$ 来转移最短路距离(因为我们去换乘点便是为了转弯),最后输出 $dis[$ 终点编号 $]-1$即可($-1$ 是为了把在终点转弯的费用减掉)。最后注意数组大小别开错就行了。(我因为这个WA了一发)
那么看代码吧!
Code:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(ch<'0'||'9'<ch) {if(ch=='-') f=-1;ch=getchar();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=getchar();}
return s*f;
}
inline int dist(int x,int y){
return x>y?x-y:y-x;
}
const int INF=1e9;
const int N=20000+3;
const int M=100002+3;
struct node{
int x,y;
}p[M];
int n,m;
int sx,sy,fx,fy;
queue<int>q;
int dis[M];
bool inq[M];
vector<int>h[N],l[N];
inline void SPFA(){//最短路
q.push(1);inq[1]=1;
for(int i=1;i<=m+2;++i){
dis[i]=INF;
}
dis[1]=0;
while(!q.empty()){
int u=q.front();q.pop();inq[u]=0;
int lena=h[p[u].x].size(),lenb=l[p[u].y].size();
//考虑转弯,可以发现往回走是不优的,但是都不违反题意,所以行列都跑一遍也没事。
for(int i=0;i<lena;++i){
int v=h[p[u].x][i],val=dist(p[v].y,p[u].y)*2+1;//费用计算
if(v==u) continue;
if(dis[v]>=dis[u]+val){
dis[v]=dis[u]+val;
if(!inq[v]){
inq[v]=1;
q.push(v);
}
}
}
for(int i=0;i<lenb;++i){
int v=l[p[u].y][i],val=dist(p[v].x,p[u].x)*2+1;
if(u==v) continue;
if(dis[v]>=dis[u]+val){
dis[v]=dis[u]+val;
if(!inq[v]){
q.push(v);
inq[v]=1;
}
}
}
}
}
int main(){
n=read();m=read();
for(int i=2;i<=m+1;++i){
p[i].x=read();p[i].y=read();
h[p[i].x].push_back(i);
l[p[i].y].push_back(i);//类似领接表的东西
}
sx=read();sy=read();fx=read();fy=read();
h[sx].push_back(1);l[sy].push_back(1);
h[fx].push_back(m+2);l[fy].push_back(m+2);//把起始点当做换乘点
p[1]={sx,sy};p[m+2]={fx,fy};
SPFA();
if(dis[m+2]!=INF) printf("%d\n",dis[m+2]-1);//除去多余的最后一次转弯费用
else printf("-1\n");//无法到达输-1
return 0;
}
似乎比其他题解的做法简单一些?