[洛谷]摇钱树 题解

前言

这是本蒟蒻切的第一道青题,觉得很有纪念意义,就出了这篇题解,文章markdown使用deepseek润色,但保证人类贡献大于AI


一、翻译题目

有 \(n\) 棵摇钱树,第 \(i\) 棵初始时有 \(m_i\) 枚金币。每过一天,第 \(i\) 棵树会掉落 \(b_i\) 枚金币,这些掉落的金币不属于这个入。
这个入 一共可以停留 \(k\) 天,每天可以选择砍掉一棵树,并获得这棵树上当时剩余的全部金币。一棵树被砍后就不能再砍。
问:在这 \(k\) 天内,这个入 最多能获得多少金币?

输入包含多组数据,以 0 0 结束(这里非常重要)


二、贪

1. 砍树的收益公式

假设 这个入 在第 \(t\) 天砍掉第 \(i\) 棵树(\(t\) 从 1 开始计数),那么这棵树已经经历了 \(t-1\) 天的掉落,剩余金币为:

\[m_i - (t-1) \times b_i \]

所以砍的时间越早,收益越高。

2. 最优砍树顺序

如果已经决定了要砍哪些树,那么这些树的砍伐顺序应该怎样安排?

设两棵树 \(i\) 和 \(j\),假设它们将在第 \(t\) 天和第 \(t+1\) 天被砍。

  • 若先砍 \(i\) 再砍 \(j\),总收益为:

    \[(m_i - (t-1)b_i) + (m_j - t b_j) \]

  • 若先砍 \(j\) 再砍 \(i\),总收益为:

    \[(m_j - (t-1)b_j) + (m_i - t b_i) \]

两者相减:

\[\begin{aligned} &[(m_i - (t-1)b_i) + (m_j - t b_j)] - [(m_j - (t-1)b_j) + (m_i - t b_i)] \\ &= - (t-1)b_i - t b_j + (t-1)b_j + t b_i \\ &= b_i - b_j \end{aligned} \]

so:

  • 如果 \(b_i > b_j\),则先砍 \(i\) 再砍 \(j\) 的收益更大。
  • 如果 \(b_i < b_j\),则先砍 \(j\) 再砍 \(i\) 的收益更大。

结论: 为了最大化收益,所有被砍的树应该按照 \(b_i\) 从大到小 的顺序砍。
也就是说,掉落速度越快的树,越要尽早砍掉,减少损失。


三、dp

确定了砍树顺序,但还需要从 \(n\) 棵树中选出若干棵来砍(最多砍 \(k\) 棵,且不能超过 \(n\) 棵)。
不能直接贪心选 \(b_i\) 最大的前 \(k\) 棵,因为初始金币 \(m_i\) 也影响收益。

1. 排序sort

先将所有树按照 \(b_i\) 从大到小排序。
排序后,如果我们决定砍某几棵树,那么它们的砍伐顺序就是它们在排序后的相对顺序。

2. 状态定义

设 \(dp[i][j]\) 表示:考虑排序后的前 \(i\) 棵树,从中选择 \(j\) 棵树砍掉所能获得的最大金币数。

其中 \(0 \le i \le n\),\(0 \le j \le \min(i, k)\)。

由于我们按 \(b_i\) 降序处理,如果第 \(i\) 棵树被选中,并且它是选中的第 \(j\) 棵树,那么它一定是在第 \(j\) 天被砍(因为前面已经选了 \(j-1\) 棵,且它们的 \(b\) 都不小于当前树)。
所以它的收益为:

\[m_i - (j-1) \times b_i \]

3. 状态转移方程

对于第 \(i\) 棵树,有两种选择:

  • 不砍它:
    继承前 \(i-1\) 棵树选 \(j\) 棵的最优值:

    \[dp[i][j] = dp[i-1][j] \]

  • 砍它:
    它将成为第 \(j\) 棵被砍的树,收益为:

    \[dp[i-1][j-1] + m_i - (j-1) \times b_i \]

因此,完整的状态转移方程为:

\[dp[i][j] = \max \Big( dp[i-1][j],\ dp[i-1][j-1] + m_i - (j-1) \times b_i \Big) \]

其中:

  • \(1 \le i \le n\)
  • \(1 \le j \le \min(i, k)\)
  • 初始状态:\(dp[0][0] = 0\),其余 \(dp[0][j] = -\infty\)
  • 对于不合法的状态(如 \(j > i\)),也视为 \(-\infty\)

最终答案:

\[ans = \max_{0 \le j \le \min(n, k)} dp[n][j] \]

因为 这个入 可以选择不砍满 \(k\) 天,所以需要取所有可能砍树数量的最大值。

4. 优化

观察转移方程,\(dp[i][j]\) 只依赖于 \(dp[i-1][j]\) 和 \(dp[i-1][j-1]\),因此可以优化为一维数组:

设 \(dp[j]\) 表示当前处理到的树中,选择 \(j\) 棵树砍掉的最大收益。

对于每一棵树 \(i\)(按 \(b_i\) 降序排序后),更新:

\[dp[j] = \max \Big( dp[j],\ dp[j-1] + m_i - (j-1) \times b_i \Big) \]

注意: 因为每棵树能切仅能砍一次,所以 \(j\) 必须倒序枚举,从大到小更新,避免同一棵树被重复使用。

枚举范围:\(j\) 从 \(\min(i, k)\) 递减到 \(1\)。

5. 边界

  • 最多能砍的树的数量为 \(K = \min(n, k)\)。
  • 初始化:\(dp[0] = 0\),其余 \(dp[j] = -\infty\)。
  • 最终为:\(\max_{0 \le j \le K} dp[j]\)。

四、复杂度分析(deepseek)

  • 排序:\(O(n \log n)\)
  • 动态规划:\(O(n \times K) \le O(n \times k)\)
  • 总时间复杂度:\(O(n \log n + nk)\),其中 \(n, k \le 1000\),最多 10 组数据,完全可行。
  • 空间复杂度:\(O(K)\),使用一维 DP 数组。

Code:

#include <bits/stdc++.h>
using namespace std;

struct Tree {
    int m, b;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, k;
    while (cin >> n >> k) {
        if (n == 0 && k == 0) break;

        vector<Tree> trees(n);
        for (int i = 0; i < n; ++i) cin >> trees[i].m;
        for (int i = 0; i < n; ++i) cin >> trees[i].b;

        // 按掉落速度 b 从大到小排序
        sort(trees.begin(), trees.end(), [](const Tree& a, const Tree& b) {
            return a.b > b.b;
        });

        int K = min(n, k);  // 最多能砍的树的数量
        const long long NEG = -1e18;
        vector<long long> dp(K + 1, NEG);
        dp[0] = 0;

        for (int i = 0; i < n; ++i) {
            int m = trees[i].m;
            int b = trees[i].b;
            // 倒序枚举 j,保证每棵树只被选一次
            for (int j = min(i + 1, K); j >= 1; --j) {
                long long val = dp[j - 1] + m - 1LL * (j - 1) * b;
                if (val > dp[j]) dp[j] = val;
            }
        }

        // 答案可以是砍 0 到 K 棵树中的任意一种情况
        long long ans = 0;
        for (int j = 0; j <= K; ++j) {
            ans = max(ans, dp[j]);
        }
        cout << ans << '\n';
    }

    return 0;
}

六、样例验证

样例 1

3 3
10 20 30
4 5 6

按 \(b\) 降序:树3(\(b=6,m=30\)),树2(\(b=5,m=20\)),树1(\(b=4,m=10\))。

  • 第1天砍树3:得 30
  • 第2天砍树2:得 \(20 - 5 = 15\)
  • 第3天砍树1:得 \(10 - 2 \times 4 = 2\)

总收益:\(30 + 15 + 2 = 47\),与输出一致。

样例 2

4 3
20 30 40 50
2 7 6 5

按 \(b\) 降序:树2(\(b=7,m=30\)),树3(\(b=6,m=40\)),树4(\(b=5,m=50\)),树1(\(b=2,m=20\))。

  • 第1天砍树2:得 30
  • 第2天砍树3:得 \(40 - 6 = 34\)
  • 第3天砍树4:得 \(50 - 2 \times 5 = 40\)

总收益:\(30 + 34 + 40 = 104\),与输出一致。


七、总结

本题的核心是:

  1. 贪心确定顺序:按掉落速度 \(b_i\) 从大到小砍。
  2. 动态规划选择树:在排序后的序列中,用 \(dp[j]\) 表示选 \(j\) 棵树的最大收益。
  3. 状态转移方程:

    \[dp[j] = \max \Big( dp[j],\ dp[j-1] + m_i - (j-1) \times b_i \Big) \]

    倒序枚举 \(j\),保证每棵树只选一次。
  4. 注意可以不砍满 \(k\) 天,最终答案取 \(dp[0 \dots \min(n,k)]\) 的最大值。

这样就能在 \(O(nk)\) 时间内高效解决问题。

posted @ 2026-10-03 17:19  Spencer20262026  阅读(7)  评论(0)    收藏  举报