思路
求从 1 到 n 的最短路,需注意的是 图上所有边的边权会实时改变
如何改变:每次走过一条边之后,所有的 x 都会 执行一次操作:x = 1/(1-x),x 是 边权,所有的 x 指所有边
即 边权会随着走过的边的数目的改变而改变
所以该题 不再仅仅是最短路问题,而是变成了 最短路 + dp 问题
不妨令 dist[i][j]:表示 从 1 到 i 经过了 j 条边的最短路
所以 我们最终只需求 min( dist[n][0],dist[n][1],dist[n][2],...... ,dist[n][M] ) M 代表最多走过的边的数目
但是 因为边是双向边,所以理论上来讲 我们可走过无穷条边 ,开数组的话 需开成 dist[点的数目][正无穷]
显然 开不到那么大
所以我们需要作进一步的 优化:
观察 x = 1/(1-x) ,我们不难发现 执行三次该操作之后,x 会变回最初的 x
所以我们可以把 走过的边的数目 视为 0 、1 、2
0:表示走过了 3x 条边 ,x>=0
1:表示走过了 3x + 1条边,x>=0
2:表示走过了 3x + 2条边,x>=0
现在 dist[点的数目] [正无穷] 变成了 dist[点的数目] [3]
最终只需求 min( dist[n][0], dist[n][1], dist[n][2] ) 便可
注意:因为边权随着 走过边的数目的改变 而改变,所以我们需额外让 cnt 也入队
一些变量的解释:
cnt:走过边的数目 ,cnt取值 0、1、2
dist[i][j]:表示 从 1 到 i 经过了 '' j 条边 '' 的最短路 ,j 取值 0、1、2
st[i][j] = true 表示 从 1 到 i 经过了 '' j 条边 '' 的最短路已经确定 ,j 取值 0、1、2
综上,Code 如下 .
Code
#include <bits/stdc++.h> using namespace std; const int N = 100010,M = 600010; struct node{ double distance; int id,cnt ; bool operator < (const node &x) const{ return distance > x.distance; } }; int n,m; bool st[N][4]; double w[M],dist[N][4]; int h[N],e[M],ne[M],idx; double change_w(double x,int t){ t=t%3; while(t--){ x = 1.0/(double)(1-x) ; } if(x<0) x=-x; return x; } void add(int a,int b,double c){ e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } double dijkstra(){ for(int i=1;i<=n;i++) dist[i][0]=dist[i][1]=dist[i][2]=1e10; // double型 不能用memset dist[1][0]=0; priority_queue<node> heap; heap.push({0,1,0}) ; while(heap.size()){ auto p=heap.top(); heap.pop(); int id=p.id,cnt=p.cnt; double distance=p.distance; if(st[id][cnt]) continue; st[id][cnt]=true; for(int i=h[id];i!=-1;i=ne[i]){ int j=e[i]; double d=change_w(w[i],cnt); if(dist[j][(cnt+1)%3]>distance+d){ dist[j][(cnt+1)%3]=distance+d; heap.push({dist[j][(cnt+1)%3],j,(cnt+1)%3}) ; } } } double res = 1e10; for(int i=0;i<3;i++) res=min(res,dist[n][i]); return res==1e10?-1:res; } int main(){ scanf("%d %d",&n,&m); memset(h,-1,sizeof h); while(m--){ int a,b,w; scanf("%d %d %d",&a,&b,&w); add(a,b,w),add(b,a,w); } double t=dijkstra(); if(t==-1) puts("-1"); else printf("%.3f\n",t); return 0; }