洛谷-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 + 前缀和剪枝 实现高效搜索。核心在于:

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

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

全部评论
还是第一次看到这个题目的平台
点赞 回复 分享
发布于 2025-12-24 21:11 北京
我每次是遇到这类题就头大
点赞 回复 分享
发布于 2025-12-24 18:58 陕西

相关推荐

2025-12-19 02:15
门头沟学院 C++
1.&nbsp;实习介绍2.&nbsp;两段开源经历拷打,主要聊开发过程遇到的事,技术涉及较少,虽然也没什么技术,估计就是确认一下是本人干的。3.&nbsp;面试官介绍自己部门不是搞数据库内核的,询问真想来吗,给面试官给予了肯定的回答。4.&nbsp;开发习惯闲聊,看不看火焰图,跨语言的benchmark怎么测的巴拉巴拉。5.&nbsp;正式开始拷打,汗流浃背了。简历上项目就是常规15445+tinykv,遇到一个也都做过的面试官相当正常。6.&nbsp;15445&nbsp;lru-k算法、crabbing&nbsp;协议(还包括读写锁细节,楼主都快记不得了,头一次有面试官问这个)。7.&nbsp;ACID&nbsp;含义(楼主顺便聊了一下CAP的C跟ACID的C区别,直接预判面试官)8.&nbsp;15445&nbsp;三种隔离级别(RU,&nbsp;RC,&nbsp;RR,这块楼主早忘记了,所以回答的是mysql和pg的实现细节,参考:https://gg2002.github.io/2025/03/16/mysql-latch,顺便扯了几嘴mysql为啥会有表级锁和binlog,因为mysql是一个分离式的架构巴拉巴拉)9.&nbsp;tinykv拷打,multi&nbsp;raft必要性,项目思想。10.&nbsp;分布式事务Percolator跟寻常单体数据库事务的差别(楼主大败而归,说到3列,但是忘记怎么具体地写这3列)11.&nbsp;raft全流程介绍(leaderelection+logreplication,楼主顺便加了点行业现状试图展示知识面)12.&nbsp;raft脑裂问题,prevote优化介绍13.&nbsp;raft的Leader&nbsp;Lease和ReadIndex优化(更是大败而归,头一次有面试官问这个,早就忘记了,扯了几嘴思想草草而过)14.&nbsp;面试官询问tinysql,楼主没做过,但楼主打过ob数据库比赛,说那个比赛sql写的多,再次跟面试官闲聊一阵15.&nbsp;广告场景题,问楼主广告曝光log和点击log哪个存kv好些,楼主说点击log少些,存点击,面试官说错,然后解释16.&nbsp;算法题,线程安全的LRU
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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