個の区間を、プチ区間たちを用いて、最小コストですべて被覆しようという問題。DP 状態の持ち方を工夫することで計算量を小さくしたい。
問題概要
個の区間があって、長さが
である。これらすべてを 2 種類の区間で被覆したい。
- 長さが
である区間が
個 (1 個あたりコストは
)
- 長さが
である区間が
個 (1 個あたりコストは
)
を用いて、 個すべての区間を被覆する方法のうち、最小コストを求めよ。
制約
考えたこと
愚直にやると次のように考えたくなる。
dp[i][j][k]
← 被覆したい区間のうちの最初の 個を被覆する方法を考える。タイプ 1 の区間を
個、タイプ 2 の区間を
個使う場合の最小コスト
しかしこれだと TLE する。状態数が あって、遷移も普通にやると
となるので、全体の計算量は
となる。
工夫
しかし、よく考えると、そもそもタイプ 1 の区間を 個、タイプ 2 の区間を
使っていると考えた時点で、コストの値は決まってしまうのだ。最小コストを求めるのに、両タイプの個数を添字に持つのは過剰だ。次のように直す。
dp[i][j][k]
← 被覆したい区間のうちの最初の 個を被覆する方法を考える。タイプ 1 の区間を
個使う場合の、タイプ 2 の区間の使用個数
タイプ 2 の区間の使用個数は添字ではなく、DP 値にしてしまう。タイプ 1 の区間の使用個数を固定したとき、「タイプ 2 の区間の使用個数」と「コスト」は綺麗に対応するため、最小コストは復元できる。
このように、DP 値を用いることで、トータルの最小コストを復元するというテクニックは、青色 Diff あたりでよく見る典型手法の一つ。
素直に実装すると、 の計算量となる。なお、
や
の解法もあるようだ。
コード
#include <bits/stdc++.h> using namespace std; #define COUT(x) cout << #x << " = " << (x) << " (L" << __LINE__ << ")" << endl 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; } int main() { int N; cin >> N; vector<long long> D(N); for (int i = 0; i < N; ++i) cin >> D[i]; vector<long long> L(2), C(2), K(2); for (int i = 0; i < 2; ++i) cin >> L[i] >> C[i] >> K[i]; const long long INF = 1LL<<60; vector<vector<long long>> dp(N+1, vector<long long>(K[0]+1, INF)); dp[0][0] = 0; for (int i = 0; i < N; ++i) { for (int k = 0; k <= K[0]; ++k) { for (int ak = 0; k + ak <= K[0]; ++ak) { long long rem = D[i] - L[0] * ak; long long need = 0; if (rem > 0) need = (rem + L[1] - 1) / L[1]; chmin(dp[i+1][k+ak], dp[i][k] + need); } } } long long res = INF; for (int k = 0; k <= K[0]; ++k) { if (dp[N][k] <= K[1]) chmin(res, C[0]*k + C[1]*dp[N][k]); } cout << (res < INF ? res : -1) << endl; }