[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\) 之和最小。形式化地说,求:

\[\large \min _ {\mathcal{T} \subseteq \{ i \} _ {i = 1} ^ {n} \land |\mathcal{T}| = l} \cfrac{\sum \limits _ {i \in \mathcal{T}} c_i}{\sum \limits _ {i \in \mathcal{T}} a_i} \cfrac{\sum \limits _ {i \not \in \mathcal{T}} c_i}{\sum \limits _ {i \not \in \mathcal{T}} 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'\) 的最小值。有两种转移:

  1. 不放到 \(\mathcal{T}\) 中。
    易得 \(f_{i, j, k} = \min \{ f_{i, j, k}, f_{i - 1, j, k} \}\)
  2. 放到 \(\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 瞎搞。

posted @ 2024-08-26 07:36  XuYueming  阅读(7)  评论(0编辑  收藏  举报