洛谷-P5194 [USACO05DEC] Scales S 题解

题目链接:https://www.luogu.com.cn/problem/P5194

题目大意

给定一个天平,最大承重为 C。有 N 个砝码,质量按非降序排列,并且从第 3 个开始,每个砝码的质量 至少等于前两个砝码质量之和(即满足类似斐波那契的性质)。要求选出若干砝码,使得它们的总质量 不超过 C,并且尽可能大。

换句话说:在满足总质量 ≤ C 的前提下,求能选出的最大砝码质量和。

关键观察

1. 砝码序列具有“快速增长”特性

题目中给出一个重要条件:

从第 3 个砝码开始,每个砝码 ≥ 前两个砝码之和。

这意味着砝码序列增长非常快(至少是斐波那契级别),因此:

  • 即使 N = 1000,实际有效的砝码数量也不会太多(因为很快就会超过 C ≤ 2^30)。
  • 更重要的是,这个性质保证了贪心或剪枝搜索的高效性

2. 暴力枚举不可行,但可以 DFS + 剪枝

直接枚举所有子集(2^N)显然不行(N=1000 太大)。但由于砝码增长快,实际递归深度很小,配合前缀和剪枝,可以高效解决。

解法思路:DFS + 前缀和优化剪枝

我们从最大的砝码开始考虑(倒序处理),对每个砝码有两种选择:

  • 不选它;
  • 选它(前提是加上它不会超过 C)。

为了加速,预处理前缀和 presum[i] = nums[0] + ... + nums[i]

剪枝策略

在递归函数 process(index, sum) 中(表示当前考虑第 index 个砝码,已选砝码总质量为 sum):

  1. 如果 sum + presum[index] <= C→ 说明从 0 到 index 的所有砝码都可以选,直接取满,更新答案并返回。
  2. 否则:先尝试不选当前砝码:process(index - 1, sum)再尝试选当前砝码(如果 sum + nums[index] <= C):process(index - 1, sum + nums[index])

由于砝码增长快,presum[index] 很快会远大于 C,所以递归树非常浅,效率很高。

正确性证明(关键)

为什么这个 DFS 能找到最优解?

  • 我们遍历了所有可能的合法组合(通过回溯)。
  • 剪枝 if (presum[index] + sum <= c) 是安全的:因为如果剩余所有砝码都能加进去还不超限,那肯定是最优的(加得越多越好),无需再分叉。
  • 由于砝码非负,目标是最大化总和,所以“能全拿就全拿”是正确的贪心选择。

此外,题目中砝码满足“类斐波那契”增长,保证了 presum 增长极快,使得剪枝极其有效,实际运行时间接近 O(N) 或 O(log C)。

代码解析

#include<iostream>
using namespace std;

long long nums[1001], presum[1001];
long long c, n, ans;

void process(int index, long long sum) {
    if (index < 0) {
        ans = max(ans, sum);
        return;
    }
    // 强剪枝:如果剩下的全部加上也不超 C,直接拿完
    if (presum[index] + sum <= c) {
        ans = max(ans, presum[index] + sum);
        return;
    }
    // 不选当前砝码
    process(index - 1, sum);
    // 选当前砝码(如果可以)
    if (sum + nums[index] <= c) {
        process(index - 1, sum + nums[index]);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> c;
    for (int i = 0; i < n; i++) {
        cin >> nums[i];
    }
    // 计算前缀和
    presum[0] = nums[0];
    for (int i = 1; i < n; i++) 
        presum[i] = presum[i - 1] + nums[i];
    
    ans = 0;
    process(n - 1, 0); // 从最后一个砝码开始
    cout << ans;
    return 0;
}

注意点

  • 使用 long long 防止溢出(虽然 C ≤ 2^30,但砝码值可能是 32 位有符号整数)。
  • 前缀和 presum[i] 表示前 i+1 个砝码的总和(0-indexed)。
  • 递归从大到小处理,便于剪枝。

复杂度分析

  • 时间复杂度:看似 O(2^N),但由于砝码快速增长 + 强剪枝,实际复杂度约为 O(N)O(log C)。例如,斐波那契数列到第 45 项就超过 2^30,所以 N 实际有效长度 ≤ 45。
  • 空间复杂度:O(N),用于存储砝码和前缀和,递归栈深度也很小。

总结

本题利用了砝码序列的快速增长性质,通过 DFS + 前缀和剪枝 实现高效搜索。核心在于:

  • 从大到小考虑砝码;
  • 若剩余砝码全选不超限,则直接取满(最优);
  • 否则分“选/不选”两种情况递归。

这种“类斐波那契”约束是本题的关键突破口,使得指数级问题变为线性可解。

全部评论

相关推荐

昨天 10:21
辽宁大学 golang
点赞 评论 收藏
分享
11-26 09:30
复旦大学 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务