公式

D - 街歩きと通行料 / City Walk and Tolls 解説 by physics0523


この問題は、俗に 拡張ダイクストラ法 と呼ばれる方法で解くことができます。
拡張ダイクストラ法とは、一見最短経路問題そのままでは上手くいかない問題に対して、グラフをうまく構築することによって通常の最短経路問題に帰着させる手法です。

今回は、 頂点倍化 というテクニックを使います。
頂点倍化とは、同じ頂点に対して意味の異なる頂点を複数作ることです。実際にやってみましょう。

  • 都市 \(k\) を頂点 \(2k-1,2k\) に分解する。
    • 頂点 \(2k-1\) を、都市 \(k\) にいて通行料を払っていない状態に対応させる。(改札外のイメージです)
    • 頂点 \(2k\) を、都市 \(k\) にいて通行料を払っている状態に対応させる。(改札内のイメージです)
  • 道路の通行時間で発生するコストを以下のように表現する。
    • 都市 \(U_i\) から都市 \(V_i\)\(W_i\) 時間かかるとすると、以下の \(2\) 辺を追加する。
      • 頂点 \(2U_i\) から頂点 \(2V_i-1\) に長さ \(W_i\) の辺を張る。
      • 頂点 \(2V_i\) から頂点 \(2U_i-1\) に長さ \(W_i\) の辺を張る。
  • 都市の通行料で発生するコストを以下のように表現する。
    • 都市 \(i\)\(C_i\) の通行料が発生するとする(通行料がかからない場合、 \(C_i=0\) とする)。
      • 頂点 \(2i-1\) から頂点 \(2i\) に長さ \(C_i\) の辺を張る。(改札を通り抜けるイメージです)

これで、通常の最短経路問題に帰着されました。求めるべきは、頂点 \(1\) から頂点 \(2N\) への最短経路です。
あとは、ダイクストラ法などを用いて解けばよいです。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
using pl=pair<ll,ll>;
using Graph=vector<vector<pl>>;

int main(){
  ll N,M,K;
  cin >> N >> M >> K;
  Graph g(2*N);
  for(ll i=0;i<M;i++){
    ll U,V,W;
    cin >> U >> V >> W;
    U--; V--;
    g[2*U+1].push_back({2*V,W});
    g[2*V+1].push_back({2*U,W});
  }
  vector<ll> pay(N,0);
  for(ll i=0;i<K;i++){
    ll L,C;
    cin >> L >> C;
    pay[L-1]=C;
  }

  for(ll i=0;i<N;i++){
    g[2*i].push_back({2*i+1,pay[i]});
  }

  vector<ll> d(2*N,8e18);
  priority_queue<pl,vector<pl>,greater<pl>> pq;
  d[0]=0;
  pq.push({0,0});
  while(!pq.empty()){
    auto od=pq.top(); pq.pop();
    if(d[od.second]!=od.first){continue;}
    if(od.second==2*N-1){
      cout << od.first << "\n";
      return 0;
    }
    for(auto &nx : g[od.second]){
      ll nd=od.first+nx.second;
      if(d[nx.first]>nd){
        d[nx.first]=nd;
        pq.push({nd,nx.first});
      }
    }
  }

  return 0;
}

投稿日時:
最終更新: