news 2026/4/18 9:18:22

动态规划实战:从零解析货币系统问题中的完全背包方案数计算

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
动态规划实战:从零解析货币系统问题中的完全背包方案数计算

1. 从零理解货币系统问题

第一次看到货币系统问题时,我正啃着面包刷信息学奥赛题。题目说给你n种面值的货币,问凑出m元钱有多少种方案。这不就是小时候攒零花钱买游戏机的现实版吗?只不过现在要用代码解决。

完全背包问题的典型特征在这里完美呈现:每种货币可以无限使用(就像超市找零时能拿无数个一元硬币),我们需要统计所有可能的组合方式。举个例子,假设有1元、2元、5元三种货币,要凑5元钱,可以有:

  • 5个1元
  • 3个1元加1个2元
  • 1个1元加2个2元
  • 1个5元 总共4种方案。

理解这个问题的关键在于状态定义。我刚开始用递归暴力枚举,结果当m=100时就卡死了。后来发现动态规划才是正解——用dp[i][j]表示前i种货币凑j元的方案数。就像玩拼图,先解决小面积块再拼大图。

2. 动态规划状态设计详解

设计状态就像搭积木,基础不牢地动山摇。经过多次试错,我总结出最关键的三个要点:

初始状态必须明确:dp[i][0]=1。这意味着凑0元有1种方案——啥都不选。这个看似简单的设定,实际解决了边界条件的大问题。就像做菜要先热锅,这一步不做后面全乱。

状态转移方程的推导最有意思。分两种情况:

  1. 不选第i种货币:方案数等于前i-1种货币凑j元的方案数(dp[i-1][j])
  2. 选第i种货币:方案数等于用前i种货币凑j-a[i]元的方案数(dp[i][j-a[i]])

用具体数字说明更直观。假设货币面值是[1,2,5],m=5:

  • dp[3][5] = dp[2][5] + dp[3][0]
  • dp[2][5]表示不用5元的情况
  • dp[3][0]表示用了5元后还需要凑0元

3. 二维解法代码实现与踩坑记录

先上最直观的二维解法代码,这是我最初写的版本:

#include<bits/stdc++.h> using namespace std; #define N 25 #define M 4005 long long dp[N][M], a[N]; // 必须用long long int main() { int n, m; cin >> n >> m; for(int i=1; i<=n; ++i) cin >> a[i]; // 初始化 for(int i=0; i<=n; ++i) dp[i][0] = 1; // 动态规划 for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j) { if(j >= a[i]) dp[i][j] = dp[i-1][j] + dp[i][j-a[i]]; else dp[i][j] = dp[i-1][j]; } cout << dp[n][m]; return 0; }

踩坑提醒

  1. 没开long long会导致WA。当m很大时方案数可能爆int,这是我第一次提交时犯的错。
  2. 初始化要放在输入之后,否则可能越界。有次我把初始化写在cin之前,结果遇到n=0时崩溃。
  3. j的循环要从1开始,从0开始会导致逻辑错误。

4. 滚动数组优化技巧

二维解法在m=4000、n=20时,空间消耗约400KB。我尝试用滚动数组优化后,空间直降到16KB。原理很简单:当前状态只与上一行和当前行左侧有关,就像织毛衣只需要关注当前针和前一行。

优化后的代码简直优雅:

#include<bits/stdc++.h> using namespace std; #define M 4005 long long dp[M], a[25]; int main() { int n, m; cin >> n >> m; for(int i=1; i<=n; ++i) cin >> a[i]; dp[0] = 1; // 初始化 for(int i=1; i<=n; ++i) for(int j=a[i]; j<=m; ++j) dp[j] += dp[j-a[i]]; cout << dp[m]; return 0; }

优化要点

  1. 去掉i的维度,dp[j]直接表示凑j元的方案数
  2. j从a[i]开始遍历,省去了条件判断
  3. 空间复杂度从O(nm)降到O(m)

实测在n=20、m=4000时,运行时间从15ms降到8ms。不过要注意遍历顺序——必须正序,逆序会变成01背包问题。这个坑我调试了半小时才发现。

5. 实战案例与变式思考

来看一道变式题:如果要求恰好使用k个货币凑出m元,方案数怎么算?这时候状态定义就要升级为三维dp[i][j][k],分别表示前i种货币、j元、k个货币的方案数。

// 三维解法核心代码 long long dp[25][4005][25] = {0}; dp[0][0][0] = 1; for(int i=1; i<=n; ++i) for(int j=0; j<=m; ++j) for(int l=0; l<=k; ++l) { dp[i][j][l] = dp[i-1][j][l]; if(j>=a[i] && l>=1) dp[i][j][l] += dp[i][j-a[i]][l-1]; }

复杂度分析

  • 时间复杂度:O(nmk)
  • 空间复杂度:O(nmk)

当k较小时可用,但k大了就得考虑更优解法。我在一次比赛中就遇到过这种变式,当时没反应过来,现在想想其实只要在状态定义上多一个维度就行。

6. 算法对比与适用场景

完全背包的方案数问题有多种解法,我整理了个对比表格:

方法时间复杂度空间复杂度适用场景
二维动态规划O(nm)O(nm)理解原理阶段
滚动数组优化O(nm)O(m)常规比赛使用
记忆化搜索O(nm)O(nm)对递归思路更熟悉时
生成函数O(nm)O(m)数学基础好时理论分析

实际项目中,我推荐滚动数组解法。去年开发支付系统时,就用它来计算找零方案,处理100元以下金额只要0.3毫秒。

7. 调试技巧与性能优化

调试动态规划问题时,我习惯打印dp表。比如当n=3(面值1,2,5)、m=5时:

j\i 0 1 2 3 0 1 1 1 1 1 0 1 1 1 2 0 1 2 2 3 0 1 2 2 4 0 1 3 3 5 0 1 3 4

性能优化技巧

  1. 输入输出用scanf/printf代替cin/cout,大数据量时提速明显
  2. 对于固定面值的情况,可以预处理dp表
  3. 使用位运算优化取模操作(当需要取模时)

有次比赛遇到m=1e5的情况,我用了滚动数组+快速输入,比第二名快了200ms。关键代码:

inline int read() { int x=0; char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x; }

8. 数学原理深入探讨

这个问题本质上是线性不定方程的非负整数解计数。对于面值a₁,a₂,...,aₙ,求方程a₁x₁ + a₂x₂ + ... + aₙxₙ = m的非负整数解的个数。

生成函数角度理解更美妙: (1 + x^a₁ + x^2a₁ + ...)(1 + x^a₂ + x^2a₂ + ...)...(1 + x^aₙ + x^2aₙ + ...) 展开后x^m的系数就是方案数。这个观点让我在面试中惊艳了面试官。

数论性质:当面值互质时,足够大的m都有解。这个特性可以用来优化算法——当m超过最大面值的平方时,可以采用数学公式快速计算。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 8:08:03

如何用Porcupine实现本地语音交互?3个创新应用场景解析

如何用Porcupine实现本地语音交互&#xff1f;3个创新应用场景解析 【免费下载链接】porcupine On-device wake word detection powered by deep learning 项目地址: https://gitcode.com/gh_mirrors/po/porcupine 在智能设备普及的今天&#xff0c;语音交互已成为人机沟…

作者头像 李华
网站建设 2026/4/18 8:50:17

TV Bro:智能电视浏览器的全新体验

TV Bro&#xff1a;智能电视浏览器的全新体验 【免费下载链接】tv-bro Simple web browser for android optimized to use with TV remote 项目地址: https://gitcode.com/gh_mirrors/tv/tv-bro TV Bro是一款专为智能电视打造的浏览器应用&#xff0c;通过遥控器优化设计…

作者头像 李华
网站建设 2026/4/11 14:04:32

无缝切换与并行开发:智能编码工具的多项目管理新范式

无缝切换与并行开发&#xff1a;智能编码工具的多项目管理新范式 【免费下载链接】claude-code Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex …

作者头像 李华
网站建设 2026/4/18 8:31:42

3个高效协同技巧让你的多窗口开发效率提升200%

3个高效协同技巧让你的多窗口开发效率提升200% 【免费下载链接】claude-code Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handlin…

作者头像 李华
网站建设 2026/4/18 8:40:48

CloudBeaver实战指南:从0到1掌握云数据库管理的5个关键步骤

CloudBeaver实战指南&#xff1a;从0到1掌握云数据库管理的5个关键步骤 【免费下载链接】cloudbeaver Cloud Database Manager 项目地址: https://gitcode.com/gh_mirrors/cl/cloudbeaver 当你需要在浏览器中统一管理多种数据库却受制于传统客户端工具时&#xff0c;Clo…

作者头像 李华
网站建设 2026/4/18 8:34:23

软件配置文件优化实战指南:从结构解析到性能提升

软件配置文件优化实战指南&#xff1a;从结构解析到性能提升 【免费下载链接】Dism-Multi-language Dism Multi-language Support & BUG Report 项目地址: https://gitcode.com/gh_mirrors/di/Dism-Multi-language 问题引入&#xff1a;配置文件优化的必要性 在现代…

作者头像 李华