前言
題目連結:洛谷。
題意簡述
給出長度為 \(n\) 的序列 \(c\) 和 \(a\),選出一個大小為 \(l\) 下標集合 \(\mathcal{T}\),使 \(i \in \mathcal{T}\) 的 \(c_i\) 之和比上 \(a_i\) 之和,乘上 \(i \not \in \mathcal{T}\) 的 \(c_i\) 之和比上 \(a_i\) 之和最小。形式化地說,求:
\(1 \leq l \lt n \leq 100\),\(1 \leq a_i \leq 100\),\(1 \leq c_i \leq 10^6\),\(\sum a_i \leq 500\)。
題目分析
發現要用揹包,具體地,我們 DP 當前選到第幾個,\(\mathcal{T}\) 中已經有了幾個元素的某些資訊。如果直接記錄 \(\cfrac{\sum c_i}{\sum a_i}\) 並不正確,也不能轉移。發現 \(a_i\) 之和很小,題目再暗示我們可以列舉它,把它記錄到狀態裡。那麼我們 DP 揹包的價值就是有關 \(\sum c_i\) 的資訊。
應該找一找性質,推一推式子。不妨記 \(A = \sum \limits _ {i = 1} ^ n a _ i\),\(C = \sum \limits _ {i = 1} ^ n c _ i\),選出的 \(c_i\),\(a_i\) 之和分別為 \(C'\),\(A'\)。我們答案就是 \(\min \cfrac{C'(C - C')}{A'(A - A')}\)。對於一個列舉了的 \(A'\),分母顯然是定值,我們求的就是分子 \(C'(C - C')\) 的最小值。發現,這是關於 \(C'\) 的二次函式,我們希望求 \(\min f(C') = -C'^2 + CC'\)。這個函式開口朝下,沒有最小值,取到最小值當然就在最小的 \(C'\) 或者最大的 \(C'\) 時取到。這樣我們 DP 記的值就是選出的 \(C'\) 的最大 / 最小值。為了方便,我們可以欽定我們求的這部分 \(C' < C - C'\),即欽定 \(l < n - l\),這樣只用記錄最小值。
總結一下,我們可以記 \(f_{i, j, k}\) 表示考慮前 \(i\) 個,選出了 \(j\) 個,\(A' = k\) 時 \(C'\) 的最小值。有兩種轉移:
- 不放到 \(\mathcal{T}\) 中。
易得 \(f_{i, j, k} = \min \{ f_{i, j, k}, f_{i - 1, j, k} \}\)。 - 放到 \(\mathcal{T}\) 中。
易得 \(f_{i, j, k} = \min\{ f_{i, j, k}, f{i - 1, j - 1, k - a_i} \}\)。
邊界顯然是 \(f_{0, 0, 0} = 0\),其他 \(f_{i, j, k} = \infty\)。
答案就是 \(\min \cfrac{f_{n, l, A'}(C - f_{n, l, A'})}{A'(A - A')}\)。
時空複雜度:\(\Theta(nlA)\)。滾一滾,空間最佳化到 \(\Theta(nA)\)。
程式碼
不小心跑到最優解。
#include <cstdio>
#include <cstring>
const int N = 105;
inline int min(int a, int b) { return a < b ? a : b; }
inline int max(int a, int b) { return a > b ? a : b; }
int n, m, A[N], C[N];
int dp[N][505];
long long son = 1, mon = 0;
inline void check(long long a, long long b) {
if (a * mon < b * son) son = a, mon = b;
}
int sum1, sum2;
signed main() {
scanf("%d%d", &n, &m), m = min(m, n - m);
for (int i = 1; i <= n; ++i) scanf("%d", &A[i]), sum1 += A[i];
for (int i = 1; i <= n; ++i) scanf("%d", &C[i]), sum2 += C[i];
memset(dp, 0x3f, sizeof dp);
dp[0][0] = 0;
for (int i = 1, s = 0; i <= n; ++i) {
s += A[i];
for (int t = min(m, i), ted = max(1, m - (n - i)); t >= ted; --t)
for (int j = A[i]; j <= s; ++j)
dp[t][j] = min(dp[t][j], dp[t - 1][j - A[i]] + C[i]);
}
for (int k = 1; k <= sum1 - 1; ++k) {
if (dp[m][k] == 0x3f3f3f3f) continue;
check(1ll * dp[m][k] * (sum2 - dp[m][k]), 1ll * k * (sum1 - k));
}
printf("%.3lf", 1. * son / mon);
return 0;
}
後記 & 反思
一道很水的揹包題,從設計狀態到思考記錄什麼的過程,再到最後的轉移方程都十分自然。但是考場上不知道為什麼沒去推只用記錄 \(C'\) 的最值,而是用了 bitset
瞎搞。