D81 分层图最短路 Dijkstra 算法 P9751 [CSP-J 2023] 旅游巴士
D81 分层图最短路 Dijkstra 算法 P9751 [CSP-J 2023] 旅游巴士_哔哩哔哩_bilibili
给一个有向图,从 1 号点开始,必须在 k 的倍数的时刻出发,到终点的时刻也必须是 k 的倍数
每条边有双边权:开放时间 $w$ 和 步行时间 $1$。到达 $u$ 点的时间 $s\ge w(u,v)$,才能通过 $(u,v)$ 边
问从 1 号点出发到 n 号时最早的时刻。(注意:不能在中间停留,可以在起点等待)
思路
如果 0 时刻出发,当前到达了 u 点,且当前时间为 s,如果 $s<w$,当前不能通过 (u,v) 边
我们可以晚 k 的倍数的时间出发,走到 u 点的时间 $\ge w$,可以通过 (u,v) 边
根据贪心思想,我们从 $x*k+s \ge w$ 中取最小的 $x$ 就行,$x=\lceil \frac{w-s}{k} \rceil$
这样到 u 点的合法的最少时间 $t=\lceil \frac{w-s}{k} \rceil*k+s$
到每个点的合法时间与 k 的倍数有关,$k\le 100$,把每个点拆成 k 个点,按 k 分层跑最短路
定义状态:$d[u,j]$ 表示到达 $u$ 点的时间 $mod\; k$ 的值为 $j$ 时的最短花费时间
答案显然是 $d[n,0]$。讨论一下边的转移,
如果 $s\ge w$,走边 $[u,s]\overset{1}{→}[v,s+1]$,转移 $d[v,(s+1)\% k]=s+1$
如果 $s<w$,先求 $t=\lceil \frac{w-s}{k} \rceil*k+s$,走边 $[u,t]\overset{1}{→}[v,t+1]$,转移 $d[v,(t+1)\% k]=t+1$
模拟走一下样例:

相关板子:
D75【模板】分层图最短路 Dijkstra 算法 P4568 [JLOI2011] 飞行路线 - 董晓 - 博客园
// 分层图最短路 Dijkstra 算法 O(nk*log(nk)) #include<bits/stdc++.h> #define pli pair<long long,int> using namespace std; const int N=10005,M=105; vector<pli> e[N]; int n,m,k; long long d[N][M]; //d[u,j]表示到达u点的时间mod k的值为j的最短花费时间 bool vis[N][M]; void dijkstra(){ memset(d,0x3f,sizeof(d)); d[1][0]=0; priority_queue<pli,vector<pli>,greater<pli> > q; q.push({0,1}); while(!q.empty()){ auto [p,u]=q.top(); q.pop(); if(vis[u][p%k]) continue; vis[u][p%k]=1; for(auto [v,w]:e[u]){ int t=(p>=w)?p:(w-p+k-1)/k*k+p; //到u点的合法的最优时间 if(d[v][(t+1)%k]>t+1){ d[v][(t+1)%k]=t+1; q.push({t+1,v}); } } } } int main(){ scanf("%d%d%d",&n,&m,&k); for(int i=0,u,v,w;i<m;i++){ scanf("%d%d%d",&u,&v,&w); e[u].push_back({v,w}); } dijkstra(); printf("%lld\n",vis[n][0]?d[n][0]:-1); }
浙公网安备 33010602011771号