1. 暴力枚举:从最直观的解法说起
第一次遇到砝码称重问题时,我下意识想到的就是暴力枚举。题目给出了6种不同重量的砝码,每种砝码有若干个,我们需要计算出所有可能的组合重量。这看起来就像是在解一个多元方程:假设使用1g砝码i1个,2g砝码i2个...20g砝码i20个,那么总重量就是i1 + 2i2 + 3i3 + 5i5 + 10i10 + 20*i20。
最直接的做法就是为每种砝码的数量都设置一个循环,嵌套起来遍历所有可能的组合。比如:
cpp复制for(int i1=0; i1<=a1; ++i1)
for(int i2=0; i2<=a2; ++i2)
for(int i3=0; i3<=a3; ++i3)
//...更多循环嵌套
这种方法虽然简单粗暴,但存在明显的性能问题。假设每种砝码平均有10个,那么循环次数将达到11^6≈1.7×10^6次。当砝码数量增加时,这个数字会呈指数级增长,这就是所谓的"组合爆炸"问题。
我在实际测试中发现,当总重量限制在1000g以内时,这种解法还能勉强应付。但一旦数据规模扩大,程序就会变得异常缓慢。这让我意识到,必须寻找更高效的算法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动态规划的引入:识别问题本质
在竞赛中遇到性能瓶颈时,动态规划(DP)往往是救命稻草。但如何判断一个问题是否适合用DP解决呢?关键要看问题是否具有以下两个特性:
- 最优子结构:问题的最优解包含子问题的最优解
- 重叠子问题:不同的决策序列会到达相同的状态
砝码称重问题恰好满足这些条件。每个砝码的选择都是独立的,且最终重量是各砝码贡献的累加。这让我联想到经典的背包问题——特别是多重背包变种,其中每种物品(砝码)有多个,但数量有限。
将问题重新表述:给定n种砝码,第i种砝码的重量为w[i],数量为a[i]。我们的目标是找出所有可能的组合重量,相当于问"用这些砝码能组成哪些不同的总重量"。
3. 状态设计与转移方程
动态规划的核心在于状态的定义和转移。对于这个问题,我经过多次尝试后确定了以下状态表示:
定义dp[i][j]为一个布尔值,表示使用前i种砝码能否称出重量j。那么状态转移可以考虑第i种砝码使用多少个:
- 不使用第i种砝码:dp[i][j]
