少し頂点を増やす感じの Dijkstra 法。とても教育的な典型問題!
問題概要
頂点数 の完全グラフが与えられる。頂点 1 から頂点 へと到達したい。途中までは社用車を使い、途中からは電車を使うことができる。電車から社用車に乗り換えることはできない。
- 頂点 から頂点 へと社用車で移動: 分かかる
- 頂点 から頂点 へと電車で移動: 分かかる
なお、社用車から電車に乗り換えるのにかかる時間は 0 分と考えてよい。頂点 1 から頂点 に到達するのにかかる最小時間を求めよ。
制約
考えたこと
頂点が 個ではなく、 個になるタイプの Dijkstra 法。
- 頂点 同士の距離は、社用車用の距離の両向辺
- 頂点 同士の距離は、電車用の距離の両向辺
- 頂点 () から頂点 に対して、距離 0 の有向辺
というグラフ上で Dijkstra 法をすればよい。頂点 または頂点 への最短路の最小値を答えればよい。
なお、密グラフなので、Dijkstra 法の距離値が最小の頂点を求める部分で、priority queue を使わずに愚直に求めた方が高速になる。その場合の計算量は となる (priority queue を使っても通るが、 となる)。
コード
#include <bits/stdc++.h> using namespace std; template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; } int main() { long long N, A, B, C; cin >> N >> A >> B >> C; vector<vector<long long>> D(N, vector<long long>(N)); for (int i = 0; i < N; ++i) for (int j = 0; j < N; ++j) cin >> D[i][j]; const long long INF = 1LL<<60; vector<long long> dp(N*2, INF), used(N*2, false); dp[0] = 0; for (int iter = 0; iter < N*2; ++iter) { // まだ使っていない頂点のうち、dp 値が最も小さいものを探す int v = -1; long long dist = INF; for (int i = 0; i < N*2; ++i) { if (used[i]) continue; if (chmin(dist, dp[i])) v = i; } if (v == -1) continue; // その頂点 v から、各頂点への移動をしていく if (v < N) { // フェーズ 2 への以降 chmin(dp[v + N], dp[v]); // 各頂点への以降 for (int v2 = 0; v2 < N; ++v2) { chmin(dp[v2], dp[v] + D[v][v2] * A); } } else { for (int v2 = N; v2 < N*2; ++v2) { chmin(dp[v2], dp[v] + D[v-N][v2-N] * B + C); } } used[v] = true; } cout << min(dp[N-1], dp[N*2-1]) << endl; }