[COCI2015-2016#6] KRUMPIRKO 题解
前言
题目链接:洛谷。
题意简述
给出长度为 \(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
瞎搞。
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18379569。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。