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;
}