动态规划 背包 之 凑钱

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

P2842 纸币问题 1

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

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

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

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

  • 不用第 i 种纸币:dp[i][j]=dp[i-1][j];
  • 用一个第 i 种纸币:dp[i][j]=dp[i][j-a[i]]+1; //前提凑的金额 大于等于 第 i 种纸币的金额,完全背包纸币数量不限,使用一个第 i 种纸币的前提 可能是已经用过第 i 种纸币,所以使用了一个第 i 种纸币之后,剩余金额 j-a[j] 依然是用前 i 种纸币去凑。
  • 两种情况求最小值
  • 初始化:dp[0][j]=INT_MAX; dp[i][0]=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:顺序不同算不同种,排列问题。

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

定义状态:dp[i] 凑出金额 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:顺序不同算同一种,组合问题。

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

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

  • 不用第 i 种纸币:dp[i-1][j]
  • 用一个第 i 种纸币:dp[i][j-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;
}
相关推荐
core5123 小时前
SGD 算法详解:蒙眼下山的寻宝者
人工智能·算法·矩阵分解·sgd·目标函数
Ka1Yan3 小时前
[链表] - 代码随想录 707. 设计链表
数据结构·算法·链表
scx201310043 小时前
20260112树状数组总结
数据结构·c++·算法·树状数组
FastMoMO3 小时前
Qwen3-VL-2B 在 RK3576 上的部署实践:RKNN + RKLLM 全流程
算法
光算科技3 小时前
AI重写工具导致‘文本湍流’特征|如何人工消除算法识别标记
大数据·人工智能·算法
星竹晨L3 小时前
【C++内存安全管理】智能指针的使用和原理
开发语言·c++
宵时待雨3 小时前
数据结构(初阶)笔记归纳3:顺序表的应用
c语言·开发语言·数据结构·笔记·算法
智者知已应修善业3 小时前
【C语言 dfs算法 十四届蓝桥杯 D飞机降落问题】2024-4-12
c语言·c++·经验分享·笔记·算法·蓝桥杯·深度优先
罗湖老棍子3 小时前
最优乘车(travel)(信息学奥赛一本通- P1377)
算法·图论·bfs·最短路·字符串流·单向边