D82 分层图最短路 Dijkstra 算法 P13271 [NOI2025] 机器人

D82 分层图最短路 Dijkstra 算法 P13271 [NOI2025] 机器人_哔哩哔哩_bilibili

 

P13271 [NOI2025] 机器人 - 洛谷

要走 $u$ 点的第 $j$ 条出边,就要先把参数 $p$ 调为 $j$,

参数 $p$ 的增减都有对应的费用,$p=1\overset{v_1}{→}p=2$,$p=2\overset{v_2}{→}p=3$;$p=2\overset{w_2}{→}p=1$,$p=3\overset{w_3}{→}p=2$

思路

如果按参数上限 k 拆点建分层图,新点数 = n*k,会爆掉

观察 $\sum d_i=m$ ,发现每个点按出边的条数拆点,增减参数就转化为在相邻拆点之间走边

没有出边的点,也要拆为一个新点,所以每个点拆为 出边条数+1 个点

图中,点 1 拆为 0,1,2,3,点 2 拆为 4,5,6,点 3 拆为 7,点 4 拆为 8,9,10...

 1. 点的相邻拆点之间连双向边,分别表示增减参数的费用

 2. 原点 $u,v$ 之间连边:

(1)如果 $u$ 的出边编号 $j\le v$ 的出边数量,那么直接连 $u,v$ 同级别的拆点,如 绿 1 连向 绿 5

(2)如果 $u$ 的出边编号 $j>v$ 的出边数量,从 $j$ 点连向 $v$ 的最大拆点,边权为 $z+\sum w$(减参的费用可以预处理前缀和),
    如 绿 3 连向 绿 10,10 号点可以继续走向其它点;
    注意:还要 绿 3 连向 绿 8 一条边权为 z 的边,表示从 黑 1 走到 黑 4,就不再移动的情况

这样,点的拆点就是分层,层与层连双向边(变参),层内连单向边(走边),建好分层图,跑一遍 Dijkstra

 image

相关板子:

D75【模板】分层图最短路 Dijkstra 算法 P4568 [JLOI2011] 飞行路线 - 董晓 - 博客园

 

// 分层图最短路 Dijkstra 算法 O(NlogN)
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;

const int N=6e5;
vector<pii> g[N],e[N];
int T,n,m,k;
int v[N],w[N],sw[N],cnt,a[N],l[N],r[N],d[N];
bool vis[N];

void dijkstra(){
  memset(d,0x3f,sizeof d),d[1]=0;
  priority_queue<pii,vector<pii>,greater<pii> > q;
  q.push({0,1});
  while(!q.empty()){
    int u=q.top().second; q.pop();
    if(vis[u])continue; vis[u]=1;
    for(auto [v,w]:e[u]){
      if(d[v]>d[u]+w){
        d[v]=d[u]+w;
        q.push({d[v],v});
      }
    }
  }
}
signed main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>T>>n>>m>>k; //n个点,m条边,k为参数上限
  for(int i=1; i<k; i++) cin>>v[i]; //v增参费用
  for(int i=2; i<=k; i++) cin>>w[i],sw[i]=sw[i-1]+w[i];//sw减参费用前缀和
  for(int i=1; i<=n; i++){
    cin>>a[i]; //i点的出边数量
    l[i]=cnt,r[i]=cnt+a[i],cnt+=a[i]+1; //i点的出边编号范围[li,ri]
    for(int j=0,y,z; j<a[i]; j++) cin>>y>>z,g[i].emplace_back(y,z); //i向y连边
  }
  for(int i=1; i<=n; i++){
    for(int j=1; j<=a[i]; j++){
      auto [y,z]=g[i][j-1];
      if(j<=a[y]) e[l[i]+j].emplace_back(l[y]+j,z); //从 i 向 y 的同级拆点连边
      else{
        e[l[i]+j].emplace_back(l[y],z); //从i的第j拆点向y的第ly拆点连边,到ly不再移动
        if(a[y]) e[l[i]+j].emplace_back(r[y],z+sw[j]-sw[a[y]]); //从i的第j拆点向y的第ry拆点连边,到ry可继续移动
      }
      if(j>1){
        e[l[i]+j-1].emplace_back(l[i]+j,v[j-1]); //i点的相邻出边点连边,增参边权v
        e[l[i]+j].emplace_back(l[i]+j-1,w[j]);   //i点的相邻出边点连边,减参边权w
      }
    }
  }
  
  dijkstra();
  for(int i=1; i<=n; i++){
    int ans=*min_element(d+l[i],d+r[i]+1);
    cout<<(ans>1e18?-1:ans)<<' ';
  }
}

 

// 分层图最短路 Dijkstra 算法 O(MlogN)
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define ipi pair<int,pii>
using namespace std;

const int N=3e5+3,inf=1e18;
int n,m,k,a[N];
vector<pii>e[N]; //动态开点连边
vector<int>d[N]; //d[u][i]表示到达u点的第i条出边时的最小费用
vector<bool>vis[N];
int sv[N],sw[N]; //增减参数费用的前缀和

int get(int x,int y){ //增减参数的费用
  if(x<=y)return sv[y-1]-sv[x-1];
  else return sw[x]-sw[y];
}
void dijkstra(){
  priority_queue<ipi,vector<ipi>,greater<ipi> > q;
  for(int i=1;i<=a[1];i++){
    d[1][i]=get(1,i); //1号点增加参数的费用
    q.push({d[1][i],{1,i}});
  }
  while(!q.empty()){
    auto [u,p]=q.top().second; q.pop();
    if(vis[u][p])continue; vis[u][p]=1;
    int v=e[u][p].first; //拿出u的第p条出边的终点v
    int cost0=d[u][p]+e[u][p].second; //d[u,p]+z
    d[v][0]=min(d[v][0],cost0); //更新不动点的费用
    
    for(int i=1;i<=a[v];i++){
      int cost=cost0+get(p,i); //d[u,p]+z+增减参数的费用
      if(d[v][i]>cost){
        d[v][i]=cost; //更新可动点的费用
        q.push({cost,{v,i}});
      }
    }
  }
}
signed main(){
  ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  int T; cin>>T>>n>>m>>k;
  for(int i=1;i<k;i++)cin>>sv[i],sv[i]+=sv[i-1];
  for(int i=2;i<=k;i++)cin>>sw[i],sw[i]+=sw[i-1];
  for(int i=1;i<=n;i++){
    cin>>a[i];
    e[i].assign(a[i]+1,{0,0});
    d[i].assign(a[i]+1,inf);
    vis[i].assign(a[i]+1,0);
    for(int j=1,y,z;j<=a[i];j++){
      cin>>y>>z;
      e[i][j]={y,z};
    }
  }
  
  dijkstra();
  for(int i=1;i<=n;i++){
    int ans=*min_element(d[i].begin(),d[i].end());
    cout<<(ans==inf?-1:ans)<<' ';
  }
}

 

posted @ 2026-02-22 14:10  董晓  阅读(189)  评论(0)    收藏  举报