思路:
这题有两种思路:Dijkstra
和SPFA
,都可以100分;在求最短路径时,Dijkstra
往往是效率比较高的解法,但是本题使用Dijkstra
的变形并不能完全贴合题意,因为这种贪心算法遇到“局部最优≠整体最优”的情况就失效了,但是不妨拿来练练手hh;
Dijkstra:
大体框架还是照搬Dijkstra
,但是注意使用long long
,唯一变形的就是另设一个数组clen[]
,用来表示第i
个结点当前是一段长为clen[i]
的短路的终点,因此在下一条长为L的短路接着这条短路时,就可以先减去clen[i]
的平方,再加上clen[i]+L
的平方了;
SPFA:
1.将大路和小路的路径分开存储,首先利用Ford算法的思想,将小路的距离数组dis_1[i][j]
全部更新为走小路的情况下,i
到j
的最短路径;
2.然后利用spfa算法的思想(其实和BFS差不多),当前路口为now
,下一个路口i
我们可以从2
遍历到n
,分三种情况前进:(1)从小路到now
,现在走大路从now
到i
;(2)从大路到now
,现在走大路从now
到i
;(3)从大路到now
,现在走小路从now
到i
;
3.最后输出min(d_0[n],d_1[n])
即可;
Dijkstra代码:
#include<bits/stdc++.h>
#define p_b(a) push_back(a)
typedef long long LL;
using namespace std;
const int MAX_N=505;
const LL INF=LLONG_MAX-100;
vector<int> node[MAX_N];
LL dis[MAX_N][MAX_N];//两个结点之间的距离
bool type[MAX_N][MAX_N];//是否是小道
LL clen[MAX_N];//到点i时连续走的小路长
LL d[MAX_N];//点i的距离
bool known[MAX_N]={false,true};//点i是否已是最短路
int n,m;
void dijkstra(){
fill(d+2,d+MAX_N,INF);
for(auto e:node[1]){
if(type[1][e]){
clen[e]=dis[1][e];
d[e]=clen[e]*clen[e];
}else d[e]=dis[1][e];
}
while(!known[n]){
int min_n;
LL min_d=INF;
for(int i=2;i<=n;i++){
if((!known[i])&&d[i]<min_d){
min_d=d[i];
min_n=i;
}
}
known[min_n]=true;
for(auto e:node[min_n]){
if(type[min_n][e]){
LL c=clen[min_n]+dis[min_n][e];
LL now=d[min_n]-clen[min_n]*clen[min_n]+c*c;
if(now<d[e]){
clen[e]=c;
d[e]=now;
}
}else{
LL now=d[min_n]+dis[min_n][e];
if(now<=d[e]){
clen[e]=0;
d[e]=now;
}
}
}
}
}
int main(){
cin>>n>>m;
memset(dis,127,sizeof(dis));
for(int i=0;i<m;i++){
int t,a,b;
LL c;
cin>>t>>a>>b>>c;
node[a].p_b(b);
node[b].p_b(a);
if(c<dis[a][b]) dis[a][b]=dis[b][a]=c;
if(t) type[a][b]=type[b][a]=true;
}
dijkstra();
cout<<d[n];
return 0;
}
SPFA代码:
#include<bits/stdc++.h>
using namespace std;
#define rp(i,n) for(int i=0;i<n;i++)
#define rpn(i,n) for(int i=1;i<=n;i++)
#define mem(a,x) memset(a,x,sizeof(a))
typedef long long LL;
const int MAX_N=505;
const LL INF=0x3f3f3f3f;
int n,m;
LL dis_0[MAX_N][MAX_N];//大路
LL dis_1[MAX_N][MAX_N];//小路
LL d_0[MAX_N],d_1[MAX_N];//前驱分别为大路和小路时,结点i的最短路径
bool vst[MAX_N];//结点i是否在队列中
void ford(){//更新小路,使得dis_1[i][j]是从小路走的情况下i到j的最短距离
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
rpn(k,n) if(dis_1[i][k]<INF&&dis_1[k][j]<INF)
dis_1[i][j]=dis_1[j][i]=min(dis_1[i][j],dis_1[i][k]+dis_1[k][j]);
}
}
}
int main(){
cin>>n>>m;
mem(dis_0,INF),mem(dis_1,INF);
mem(d_0,INF),mem(d_1,INF);
d_0[1]=d_1[1]=0;
rp(i,m){
int t,a,b;
LL c;
cin>>t>>a>>b>>c;
if(t&&c<dis_1[a][b]) dis_1[a][b]=dis_1[b][a]=c;
else if(!t&&c<dis_0[a][b]) dis_0[a][b]=dis_0[b][a]=c;
}
ford();
queue<int> q;
q.push(1);
while(!q.empty()){//spfa
int now=q.front();
q.pop();
vst[now]=false;
for(int i=2;i<=n;i++){//从now走到i
bool update=false;
if(dis_0[now][i]<INF){
LL d=d_0[now]+dis_0[now][i];//之前走大路,现在还是从大路走到i
if(d<d_0[i]){
d_0[i]=d;
update=true;
}
d=d_1[now]+dis_0[now][i];//之前走小路,现在走大路
if(d<d_0[i]){
d_0[i]=d;
update=true;
}
}
if(dis_1[now][i]<INF){
LL d=d_0[now]+dis_1[now][i]*dis_1[now][i];//之前走大路,现在从小路走到i
if(d<d_1[i]){
d_1[i]=d;
update=true;
}
}
if(update&&!vst[i]){
q.push(i);
vst[i]=true;
}
}
}
cout<<min(d_0[n],d_1[n]);
return 0;
}