「ABC 301 E - Pac-Takahashi」の類題とも言うべき ICPC 系の問題!
問題概要
頂点数 、辺数 の重み付き無向グラフが与えられる。頂点番号は とする。 番目の辺は頂点 を結んでおり、その重みは である。
このグラフの頂点のうち 個の特別な頂点 があって、その頂点には SIRO というラーメン店がある。これらの頂点に立ち寄ったとき、ラーメンを食べるのに要する時間は である。
さて、あなたは今、頂点 にいる。いまからあなたは 秒以内にグラフを移動して、できるだけ多くの SIRO のある頂点でラーメンを食べて、最終的に頂点 に帰って来たい。
食べることのできるラーメン数の最大値を求めよ。
制約
考えたこと
すべて 0-indexed で考える。SIRO というラーメン店のある頂点を SIRO 頂点と呼ぶことにする。
元のグラフに対して、SIRO 頂点と、スタート頂点からなる、頂点数 のグラフを作る。
- 頂点 は、元のグラフの頂点 に対応する
- 頂点 は、元のグラフの頂点 に対応する
- 辺 の重みは、元のグラフ上で対応する頂点間の最短距離とする
こうしたグラフは、 個の頂点を始点とした Dijkstra 法によって、 の計算量で構築できる。
元の問題は、このグラフ上で TSP に近いことをする問題となる。
このグラフは頂点数が であり、とても頂点数が小さいという特徴をもつ。よって、次の配列 dp
を求める bit DP で間に合う。
dp[S][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<int, long long>; using Graph = vector<vector<Edge>>; const long long INF = 1LL<<60; // 頂点 s を始点としたグラフ探索 vector<long long> Dijkstra(const Graph &G, int s) { vector<long long> dist(G.size(), INF); dist[s] = 0; priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<pair<long long,int>>> que; que.push({0, s}); while (!que.empty()) { auto [cur, v] = que.top(); que.pop(); if (dist[v] < cur) continue; for (const auto &e : G[v]) { if (chmin(dist[e.first], dist[v] + e.second)) { que.push({dist[e.first], e.first}); } } } return dist; } int solve(int N, int M, int L, int S, int T) { --S; Graph G(N); for (int i = 0; i < M; ++i) { long long a, b, c; cin >> a >> b >> c; --a, --b; G[a].emplace_back(b, c); G[b].emplace_back(a, c); } // SIRO info vector<long long> J(L), E(L); for (int i = 0; i < L; ++i) { cin >> J[i] >> E[i]; --J[i]; } J.push_back(S), E.push_back(0); int s = L++; // make graph vector<vector<long long>> G2(L, vector<long long>(L, INF)); for (int i = 0; i < L; ++i) { const auto &dist = Dijkstra(G, J[i]); for (int j = 0; j < L; ++j) G2[i][j] = dist[J[j]]; } // bit DP: dp[S][v] := 頂点集合 S をすべて通って、最後にいる頂点が v であるときの、最小コスト vector<vector<long long>> dp(1<<L, vector<long long>(L, INF)); dp[1<<s][s] = 0; for (int bit = 0; bit < (1<<L); ++bit) { for (int v = 0; v < L; ++v) { for (int v2 = 0; v2 < L; ++v2) { int nbit = bit | (1<<v2); dp[nbit][v2] = min(dp[nbit][v2], dp[bit][v] + G2[v][v2] + E[v2]); } } } // dp[bit][g] <= T となる bit について、最多お菓子数を求める int res = -1; for (int bit = 0; bit < (1<<L); ++bit) { for (int v = 0; v < L; ++v) { if (dp[bit][v] + G2[v][s] <= T) chmax(res, __builtin_popcount(bit) - 1); } } return res; } int main() { int N, M, L, S, T; while (cin >> N >> M >> L >> S >> T, N) { cout << solve(N, M, L, S, T) << endl; } }