把每种纸币看作可以无限使用的物品,dp[j]=min(dp[j],dp[j-v]+1) 正序枚举金额求最少张数。
OJ: luogu
题目 ID: P2842
难度:普及-
标签:动态规划完全背包背包
日期: 2026-08-08 23:13
题意
有
| 原题对象 | 背包含义 |
|---|---|
| 一种纸币 | 一个可以重复使用的物品 |
| 面额 |
物品体积 |
| 张数 | 物品个数 |
| 凑出金额 |
恰好装满容量 |
思路
一句话本质: 完全背包求最少物品数——容量正序枚举,dp 初始化为 INF,唯一合法起点 dp[0]=0。
先看最直接的暴力:
// brute.cpp:小数据暴力解,使用递归枚举每种纸币的使用张数,求最少总张数。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
const int INF = 0x3f3f3f3f;
int n; // 纸币种类数
int w; // 要凑出的金额
int a[MAXN]; // 每种纸币的面额
int answer; // 最少张数
// dep:当前正在决定第 dep 种纸币的使用张数
// used:已经使用的纸币总张数
// remain:剩余需要凑出的金额
void dfs(int dep, int used, int remain) {
if (dep == n + 1) {
if (remain == 0 && used < answer)
answer = used;
return;
}
// 枚举第 dep 种纸币用 cnt 张
int max_cnt = remain / a[dep];
for (int cnt = 0; cnt <= max_cnt; cnt++) {
dfs(dep + 1, used + cnt, remain - cnt * a[dep]);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> w;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
answer = INF;
dfs(1, 0, w);
cout << answer << '\n';
return 0;
}这个暴力对每种纸币枚举使用张数,递归到叶子后检查剩余金额是否为 0,并更新最少张数。复杂度指数级,只适合小数据验证。
这个暴力卡在哪里?
对所有纸币依次决定张数,分支太多。对于
能否先不看纸币种类,只关心"凑出某个金额的最少张数"?
可以。设
一张纸币可以被重复使用多次,转移时怎么体现?
如果正序枚举
什么时候能保证
因为所有转移都只走"加一张纸"的操作,
DP 公式
设
样例 DP 表格
以样例 2 为例:
| 处理纸币 | 面额 | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| 初始 | — | 0 | INF | INF | INF | INF | INF | INF | INF |
| 纸币 1 | 1 | 0 | 1 | 5 | 6 | 10 | 11 | 12 | 15 |
| 纸币 2 | 5 | 0 | 1 | ||||||
| 纸币 3 | 11 | 0 | 1 | 1 | 2 | 2 |
答案
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
const int MAXW = 10005;
const int INF = 0x3f3f3f3f;
int n; // 纸币种类数
int w; // 要凑出的金额
int a[MAXN]; // 每种纸币的面额
int dp[MAXW]; // dp[j] = 凑出金额 j 最少需要的纸币张数
void read_input() {
cin >> n >> w;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
}
void solve() {
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i <= n; i++) {
// 完全背包正序枚举,每种纸币可以无限使用。
for (int j = a[i]; j <= w; j++) {
dp[j] = min(dp[j], dp[j - a[i]] + 1);
}
}
cout << dp[w] << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_input();
solve();
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题是完全背包求最小值的标准模板。关键点:
- dp 初始化为 INF,dp[0]=0
- 正序枚举金额,允许同种纸币重复使用
- 最终转移:
与完全背包计数题(纸币问题 2、3)相比,这里求的是 最小值,初始化不同但转移思想一致。