news 2026/4/3 4:53:35

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

作者头像

张小明

前端开发工程师

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/3/13 12:42:11

Fillinger:提升设计师效率的智能填充工具创意工作流指南

Fillinger&#xff1a;提升设计师效率的智能填充工具创意工作流指南 【免费下载链接】illustrator-scripts Adobe Illustrator scripts 项目地址: https://gitcode.com/gh_mirrors/il/illustrator-scripts 你是否曾在Illustrator中手动排列数百个图形元素&#xff0c;只…

作者头像 李华
网站建设 2026/3/31 3:04:48

3大核心价值助力药物研发:蛋白质结合位点分析工具全攻略

3大核心价值助力药物研发&#xff1a;蛋白质结合位点分析工具全攻略 【免费下载链接】fpocket fpocket is a very fast open source protein pocket detection algorithm based on Voronoi tessellation. The platform is suited for the scientific community willing to deve…

作者头像 李华
网站建设 2026/3/30 4:43:42

MaxKB+MinerU:构建自动化知识库的PDF解析与存储全流程指南

1. 为什么需要PDF解析与知识库自动化 处理PDF文档一直是企业知识管理中的痛点。想象一下&#xff0c;你手头有几百份产品手册、技术文档和合同&#xff0c;需要从中快速找到某个参数或条款。传统方式是手动翻阅或简单搜索&#xff0c;效率低下且容易遗漏关键信息。这就是为什么…

作者头像 李华
网站建设 2026/4/3 0:30:26

语音转录效率革命:faster-whisper重新定义音频处理速度

语音转录效率革命&#xff1a;faster-whisper重新定义音频处理速度 【免费下载链接】faster-whisper 项目地址: https://gitcode.com/gh_mirrors/fas/faster-whisper 你是否曾遇到过这样的困境&#xff1a;花了整整一个小时等待一段会议录音的转录结果&#xff1f;或者…

作者头像 李华
网站建设 2026/3/21 11:54:29

Leetcode 33. 搜索旋转排序数组 (Day 23) JavaScript

var search function (nums, target) {let left 0, right nums.length - 1;while (left < right) {const mid left Math.floor((right - left) / 2);if (nums[mid] target) return mid;// 左半段有序if (nums[left] < nums[mid]) {// target 在 [nums[left], nums[…

作者头像 李华
网站建设 2026/3/31 3:50:32

5个核心模块带你掌握SteamStub逆向分析:开源技术研究工具实践指南

5个核心模块带你掌握SteamStub逆向分析&#xff1a;开源技术研究工具实践指南 【免费下载链接】Steam-auto-crack Steam Game Automatic Cracker 项目地址: https://gitcode.com/gh_mirrors/st/Steam-auto-crack 技术研究免责声明 本文所涉及的技术分析仅用于逆向工程学…

作者头像 李华