https://atcoder.jp/contests/abc164/tasks/abc164_e
前提知識
解説
https://atcoder.jp/contests/abc164/submissions/12401175
まず、今回の問題はダイクストラを理解していないと解けない。
そちらが分からない場合は、より簡単な問題でダイクストラを勉強することをオススメする。
以下、拡張ダイクストラについて少し触れてから解説する。
拡張ダイクストラとは何だろう。
端的に言うと、普通のダイクストラは(頂点)を状態として最短距離を求めるが、
拡張ダイクストラでは、(頂点,何か)のように複数要素で1状態として考えて、最短距離を求めるというもの。
それだけである。
今回は、「(頂点, 持っている銀貨の枚数)を状態として、ある頂点にいて、ある銀貨の枚数を持っているときの
これまでにかかった時間の最短を求める」ということをする。
なぜこれができるか考えてみよう。
今回の問題で最も見落としてはいけない制約が「A[i]≦50」である。
仮に1回も金貨の交換を行わなかった場合に必要な銀貨は頂点が50個あり、各辺で最大50銀貨必要なので、
2500枚くらいが必要な最大枚数となる。
逆に言うと、3000枚以上くらいあれば、絶対金貨を交換する必要はない。
よって、Sが109の上限があっても、持っている銀貨は3000枚以下を想定すれば十分であるということになる。
よって、ダイクストラで(頂点, 持っている銀貨の枚数)というのを状態とするが、状態数は50*3000くらいで、
計算できる量になる。
さて、ダイクストラできそうな見た目になってきたので、後は遷移を考える。
頂点から頂点へ普通に遷移するのは問題ないとして、金貨の交換はどう遷移させるかというのが問題になる。
例えば銀貨を3枚持っていて、今いる都市では金貨1枚を銀貨4枚にできるとする。
すると、遷移は7,11,15,19,...枚への遷移があるように見えるが、実際は金貨1枚の交換だけを考えればいい。
11枚への遷移が必要ではないか?と考えるかもしれないが、他の状態として銀貨7枚持っている状態も別で考えるので、
そこからの遷移で11枚はカバーすればいい。
このように複数回動作を行える場合であっても、1回だけ動作を行うことで連鎖的に複数回の操作をカバーできたりする。
これは典型テクなので、覚えておくといいだろう。
あとは、ダイクストラの実装を頑張ると答え。
int N, M, S; vector<pair<int,int>> E[50]; int A[101], B[101], C[50], D[50]; ll dist[50][3010]; template<typename T> using min_priority_queue = priority_queue<T, vector<T>, greater<T>>; bool vis[50][3010]; ll ans[50]; //--------------------------------------------------------------------------------------------------- void _main() { cin >> N >> M >> S; chmin(S, 3000); rep(i, 0, M) { int u, v; cin >> u >> v; u--; v--; cin >> A[i] >> B[i]; E[u].push_back({ v, i }); E[v].push_back({ u, i }); } rep(i, 0, N) cin >> C[i] >> D[i]; rep(i, 0, N) rep(s, 0, 3001) dist[i][s] = infl; rep(i, 0, N) rep(s, 0, 3001) vis[i][s] = false; rep(i, 0, N) ans[i] = infl; min_priority_queue<pair<ll, int>> que; dist[0][S] = 0; que.push({ 0, 0 * 5010 + S }); while (!que.empty()) { auto q = que.top(); que.pop(); ll cst = q.first; int cu = q.second / 5010; int s = q.second % 5010; if (vis[cu][s]) continue; vis[cu][s] = true; chmin(ans[cu], cst); fore(p, E[cu]) { int to = p.first; int i = p.second; if (s < A[i]) continue; ll cst2 = cst + B[i]; if (chmin(dist[to][s - A[i]], cst2)) que.push({ dist[to][s - A[i]], to * 5010 + s - A[i] }); } ll cst2 = cst + D[cu]; int s2 = min(3000, s + C[cu]); if (chmin(dist[cu][s2], cst2)) que.push({ dist[cu][s2], cu * 5010 + s2 }); } rep(i, 1, N) printf("%lld\n", ans[i]); }