D80 分层图最短路 Dijkstra 算法 AT_abc164_e Two Currencies
D80 分层图最短路 Dijkstra 算法 AT_abc164_e Two Currencies_哔哩哔哩_bilibili
AT_abc164_e [ABC164E] Two Currencies - 洛谷
思路
边上的转移花费币和时间,点上购买币花费时间,点上的币必须 $>=$ 边上的币才能走
设 d[u,j] 表示到达 u 点且有 j 个币的最小花费时间
1、u,v 之间有需花费 x 个币和 t 秒的边, $u\overset{x,t}{→}v$
如果 u 点的币 $j>=x$,那么可以走到 v 点, $d[u,j] \overset{x,t}{→} d[v,j−x]$, 即 $d[v,j−x]=d[u,j]+t$
2、u 点上购买 c 个币需要花费 t 秒,拆点连边,把点权化为边权, $u\overset{c,t}{→}u$
如果 u 点原有 $j$ 个币 ,那么, $d[u,j] \overset{c,t}{→} d[u,j+c]$, 即 $d[u,j+c]=d[u,j]+t$
观察到点数和边上币都只有 50,所以就拆点建分层图,跑 Dijkstra
把数组的二维转化为一维更方便,$u\in [1,50]$,$x\in [1,50]$,考虑最坏情况,1 到 n 之间有 49 条边的币均需 50,
又必须在 1 号点买够所有币(因为其他点买币耗时都太大 $10^9$),所以点上有币的上限为 $B=2450,j\in[0,2450]$
把状态点 $[u,j]$ 映射为新点 $(u-1)*(B+1)+j$,点 1 拆成 $[0,B]$,点 2 拆成 $[B+1,2B+1]$,都是 2451个新点
状态点$[u,j]、[v,j−x]$ 之间的连边转化为新点之间的连边,注意合法范围为 $j\in[x,B]$
状态点$[u,j]、[u,j+c]$ 之间的连边转化为新点之间的连边,注意合法范围为 $j\in[0,B-c]$
在 u 点上多次买币,等价于等差点之间多次连单向边,例如,$c=3,j=0,[u,0] \overset{t}{→} [u,3]\overset{t}{→} [u,6]\overset{t}{→} [u,9]$
模拟一下样例 #1 的部分建图:

最后,估计一下新图的点数和边数:点数 $=50*2450<2e5$,边数 $=100*2450*2+50*2450<7e5$
相关板子:
D75【模板】分层图最短路 Dijkstra 算法 P4568 [JLOI2011] 飞行路线 - 董晓 - 博客园
// 分层图最短路 Dijkstra 算法 O(MlogN) #include<bits/stdc++.h> #define int long long #define pii pair<int,int> using namespace std; const int N=2e5,M=7e5,B=2450; //B是花费银币的上限 int h[N],to[M],w[M],ne[M],idx; void add(int a,int b,int c){ to[++idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx; } int n,m,s; int d[N]; bool vis[N]; int get(int u,int j){ //点u有j个币的映射点编号 return (u-1)*(B+1)+j; //u:1~50,j:0~B,B=2450 } void dijkstra(){ memset(d,0x3f,sizeof d); d[get(1,min(s,B))]=0; priority_queue<pii,vector<pii>,greater<pii> > q; q.push({0,get(1,min(s,B))}); while(!q.empty()){ int u=q.top().second; q.pop(); if(vis[u]) continue; vis[u]=1; for(int i=h[u]; i; i=ne[i]){ int v=to[i]; if(d[v]>d[u]+w[i]){ d[v]=d[u]+w[i]; q.push({d[v],v}); } } } } signed main(){ cin>>n>>m>>s; for(int i=1,u,v,x,t; i<=m; i++){ cin>>u>>v>>x>>t; //边(u,v)花费x个币和t秒 for(int j=x; j<=B; j++){ add(get(u,j),get(v,j-x),t); //u到v的合法映射点连权值为t的边 add(get(v,j),get(u,j-x),t); //v到u的合法映射点连权值为t的边 } } for(int i=1,c,t; i<=n; i++){ cin>>c>>t; //买c个币,花费t秒 for(int j=0; j+c<=B; j++){ add(get(i,j),get(i,j+c),t); //i点拆成等差的映射点连权值为t的边 } } dijkstra(); for(int i=2; i<=n; i++){ int ans=1e16; for(int j=0; j<=B; j++)ans=min(ans,d[get(i,j)]); //答案在点i的映射点中 cout<<ans<<endl; } }
浙公网安备 33010602011771号