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 的部分建图:

image

最后,估计一下新图的点数和边数:点数 $=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;
  }
}

 

posted @ 2026-02-21 21:56  董晓  阅读(131)  评论(0)    收藏  举报