こういうのを素早く解けるようになりたい
問題概要
頂点 辺の重み付き無向グラフがある。各頂点 には
- そこで売ってるパンを買ったときの美味しさ
- そこで売ってるジャムの種類
- そこで売ってるジャムを買ったときの美味しさ
という 3 つの値がある。ジャムの種類は のいずれかであることが保証されている。各 に対して、獲得する幸福度の最大値を求めよ。初期状態の幸福度を 0 として
- 頂点 1 を出発して
- ある頂点 を訪れる (幸福度が、たどった辺の重みの和だけ減少)
- 頂点 でパンを買う (幸福度が、 だけ増加)
- 売ってるジャムの種類が であるような、ある頂点 を訪れる (幸福度が、たどった辺の重みの和だけ減少)
- 頂点 でジャムを買う (幸福度が、 だけ増加)
- 頂点 1 に戻る (幸福度が、たどった辺の重みの和だけ減少)
という操作を行う。
制約
考えたこと
とりあえず、ジャムを買う頂点については決め打ちして、 通りすべて試すことにする。このとき、パンを買う頂点もすべて決め打ちしてしまうと TLE しそうである。
そこで、次の値を管理する (幸福度を符号反転してコストと読み替えている)
- dp1[ v ] := 頂点 1 から出発して、途中でパンを買わずに、頂点 v まで訪れるときの最小コスト
- dp2[ v ] := 頂点 1 から出発して、途中どこかでパンを買った場合の、頂点 v まで訪れるときの最小コスト
つまり、頂点を「パンをまだ買ってない」「パンをすでに買った」という情報を持たせて倍加する。こういうの、古の競プロ界では拡張 Dijkstra 法などと呼ばれたりしていた。現在はそのネーミングはよくないよね、ってことになっている。
それはさておき、このままだと「パンを買う」という行為を表す遷移の重みが負になるので Dijkstra 法を使えなくなってしまう。Dijkstra 法が使えないと計算量的にやばそう。そこで、すべてのパンに一律に下駄を履かせて非負になるようにする。パンを買う個数はかならず 1 個なので、下駄を履かせて問題ない。
そして各頂点 v に対して、
J[ v ] - dp1[ v ] - (dp2[ v ] - GETA)
が v でジャムを買う場合の幸福度の最大値となる。
コード
計算量は となる。
#include <bits/stdc++.h> using namespace std; template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; } template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; } using Edge = pair<long long, int>; using Graph = vector<vector<Edge>>; int main() { int N, M, K; cin >> N >> M >> K; vector<long long> P(N), C(N), J(N); for (int i = 0; i < N; ++i) cin >> P[i]; for (int i = 0; i < N; ++i) cin >> C[i]; for (int i = 0; i < N; ++i) cin >> J[i]; Graph G(N); for (int i = 0; i < M; ++i) { int u, v; long long t; cin >> u >> v >> t; --u, --v; G[u].emplace_back(v, t); G[v].emplace_back(u, t); } const long long INF = 1LL<<60; const long long GETA = 1LL<<30; vector<long long> dp1(N, INF), dp2(N, INF); // notuse pan, use pan using pint = pair<int,int>; // vertex, (notuse pan: 0, use pan: 1) priority_queue<pair<long long, pint>, vector<pair<long long, pint>>, greater<pair<long long, pint>>> que; dp1[0] = 0; que.push({0, pint(0, 0)}); while (!que.empty()) { auto p = que.top(); que.pop(); long long cur = p.first; int v = p.second.first, parity = p.second.second; if (parity == 0 && cur > dp1[v]) continue; if (parity == 1 && cur > dp2[v]) continue; if (parity == 0) { if (chmin(dp2[v], dp1[v] - P[v] + GETA)) { que.push({dp2[v], pint(v, 1)}); } for (auto e : G[v]) { int nv = e.first, w = e.second; if (chmin(dp1[nv], dp1[v] + w)) { que.push({dp1[nv], pint(nv, 0)}); } } } else { for (auto e : G[v]) { int nv = e.first, w = e.second; if (chmin(dp2[nv], dp2[v] + w)) { que.push({dp2[nv], pint(nv, 1)}); } } } } vector<long long> res(K, -INF); for (int v = 0; v < N; ++v) { chmax(res[C[v]-1], J[v] - (dp1[v] + dp2[v] - GETA)); } for (auto v : res) cout << v << endl; }