动态规划 背包 之 凑钱

P2842 纸币问题 1
P2840 纸币问题 2
P2834 纸币问题 3

P2842 纸币问题 1

某国有 n 种纸币,每种纸币面额为 a i a_i ai 并且有无限张,现在要凑出 w 的金额,试问最少用多少张纸币可以凑出来?(保证可以凑出对应金额)

解析:纸币数量不限制,典型的完全背包问题,求凑出某个金额的 "最少纸币数" 。

背包问题思考的核心:用不用当前元素

定义状态:dpij 前 i 种纸质凑出金额 j 的最少纸币数

  • 不用第 i 种纸币:dpij=dpi-1j;
  • 用一个第 i 种纸币:dpij=dpij-a\[i]+1; //前提凑的金额 大于等于 第 i 种纸币的金额,完全背包纸币数量不限,使用一个第 i 种纸币的前提 可能是已经用过第 i 种纸币,所以使用了一个第 i 种纸币之后,剩余金额 j-aj 依然是用前 i 种纸币去凑。
  • 两种情况求最小值
  • 初始化:dp0j=INT_MAX; dpi0=0; //0个硬币凑非0金额 j 元,不可能凑出,用最大值表示;用 i 种纸币凑0,一张纸币都不用,初始化为0。
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n, w, a[N];
int dp[N][10*N]; //dp[i][j] 前 i 种纸质凑出金额 j 的最少纸币数
int main() {
	cin>>n>>w;
	for(int i=1; i<=n; i++) cin>>a[i];
	for(int j=1; j<=w; j++) dp[0][j]=INT_MAX;
	//完全背包问题 
	for(int i=1; i<=n; i++){
		for(int j=1; j<=w; j++){
			dp[i][j]=dp[i-1][j]; //不用第i种纸币 
			if(j>=a[i] && dp[i][j-a[i]]!=INT_MAX){ //用一个第i种纸币 
				dp[i][j]=min(dp[i][j], dp[i][j-a[i]]+1);
			}
		}
	}
	cout<<dp[n][w];
	return 0;
}

状态压缩版本

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n, w, a[N];
int dp[10*N]; //dp[j] 前 i 种硬币 凑出金额 j 的最少纸币数
int main() {
	cin>>n>>w;
	for(int i=1; i<=n; i++) cin>>a[i];
	for(int j=1; j<=w; j++) dp[j]=INT_MAX; //求最小值,初始化为最大 
	//完全背包问题 
	dp[0]=0;  //凑0元一张也不用 
	for(int i=1; i<=n; i++){
		for(int j=a[i]; j<=w; j++){
			if(dp[j-a[i]]!=INT_MAX){
				//用一张 第 j 种硬币 和 不用 求最小值 
				dp[j]=min(dp[j], dp[j-a[i]]+1);
			}
		}
	}
	cout<<dp[w];
	return 0;
}

P2840 纸币问题 2

有 n 种面额互不相同的纸币,第 i 种纸币的面额为 a i a_i ai 并且有无限张,现在需要支付 w 的金额,求问有多少种方式可以支付面额 w,答案对 10 9 + 7 10^9+7 109+7 取模。

注意在这里,同样的纸币组合如果支付顺序不同,会被视作不同的方式。例如支付 3 元,使用一张面值 1 的纸币和一张面值 2 的纸币会产生两种方式(1+2 和 2+1)。

P2834 纸币问题 3

有 n 种面额互不相同的纸币,第 i 种纸币的面额为 a i a_i ai 并且有无限张,现在需要支付 w 的金额,请问有多少种纸币组合能恰好支付金额 w,答案对 10 9 + 7 10^9+7 109+7 取模。

解析:纸币问题2 和 纸币问题3 的区别在于支付顺序不同的方式是否为同一种方式,若认为不是同一种方式,则属于排列问题;若认为是同一种方式,则属于组合问题。

纸币问题 2:顺序不同算不同种,排列问题。

  • 核心思想:统计所有可能的支付顺序,同一组合不同顺序视为不同方案。
  • 实现要点:先遍历金额,再遍历硬币,每次新增硬币都能在前序金额基础上生成新排列。

定义状态:dpi 凑出金额 i 的所有排列数,凑出 i 元的总方式数是 i 分别减去每种纸币面额的方式数的和

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
const int MOD=1e9+7;
int n, w, a[N];
int dp[10*N]; //dp[i] 支付金额 j 的所有排列数
int main() {
	cin>>n>>w;
	for(int i=1; i<=n; i++) cin>>a[i];

	dp[0]=1; //支付0元,只有 一张纸币不用 这一种方式
	for(int i=1; i<=w; i++) {
		for(int j=1; j<=n; j++) {
			if(i>=a[j]) {
				dp[i]=(dp[i]+dp[i-a[j]])%MOD;
			}
		}
	}
	cout<<dp[w];
	return 0;
}

纸币问题 3:顺序不同算同一种,组合问题。

  • 核心思想:每种硬币按 "选或不选" 的方式转移,避免重复统计同一组合的不同顺序。
  • 实现要点:先遍历硬币,再遍历金额,确保每种硬币的使用不交叉影响顺序

定义状态:dpij 前 i 种纸质凑出金额 j 的方法数

  • 不用第 i 种纸币:dpi-1j
  • 用一个第 i 种纸币:dpij-a\[i];
  • 两种情况求和
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
const int MOD=1e9+7;
int n, w, a[N];
int dp[N][10*N]; //dp[i][j] 前 i 种纸质凑出金额 j 的方法数
int main() {
	cin>>n>>w;
	for(int i=1; i<=n; i++) cin>>a[i];
	//完全背包问题 
	dp[0][0]=1; //0种纸币凑0元,1种方法 
	for(int i=1; i<=n; i++){
		for(int j=0; j<=w; j++){
			dp[i][j]=dp[i-1][j]; //不用第 i 种纸币 
			if(j>=a[i]){ //用 1 张第 i 种纸币 
				dp[i][j]=(dp[i][j]+dp[i][j-a[i]])%MOD; 
			} 
		}
	}
	cout<<dp[n][w];
	return 0;
}

状态压缩版本

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
const int MOD=1e9+7;
int n, w, a[N];
int dp[10*N]; //dp[j] 前 i 种纸质凑出金额 j 的方法数
int main() {
	cin>>n>>w;
	for(int i=1; i<=n; i++) cin>>a[i];
	//完全背包问题 
	dp[0]=1; //0种纸币凑0元,1种方法 
	for(int i=1; i<=n; i++){
		for(int j=a[i]; j<=w; j++){
			//用一张 第 j 种硬币 和 不用 求和 
			dp[j]=(dp[j]+dp[j-a[i]])%MOD;
		}
	}
	cout<<dp[w];
	return 0;
}
相关推荐
apocelipes10 小时前
常用编程语言和库的正则表达式性能对比
c语言·c++·python·性能优化·golang·开发工具和环境
HjhIron11 小时前
面试常客:字符串算法从入门到进阶
算法·面试
吴佳浩13 小时前
DeepSeek DSpark:Confidence-Scheduled Speculative Decoding 技术解析
人工智能·算法·deepseek
触底反弹14 小时前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
vivo互联网技术18 小时前
ICLR 2026 | 基于后验采样的图像恢复方法LearnIR:人脸去阴影、去雾
人工智能·算法·aigc
浮生望20 小时前
JS字符串与回文算法:从包装类到双指针的面试进阶之路
javascript·算法
黄敬峰20 小时前
面试必刷:从JS底层包装类到双指针,彻底搞懂字符串与回文算法
算法
地平线开发者1 天前
J6B vio scenario sample
算法
BothSavage2 天前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法