完全背包计数:外层按面值分组、金额正序执行 ways[j] += ways[j-c],统计凑出金额 N 的组合数。
OJ: roj
题目 ID: 2033
难度:普及-
标签:动态规划完全背包组合计数python
创建: 2026-10-01 04:15
更新: 2026-10-07 13:50
前置题目
luogu U661988疯狂的背包问题(4) - 完全背包问题B 把 A 教的「一维数组容量/金额正序枚举,让 dp[c-v] 已含本物品贡献」这一完全背包关键步骤,直接用到计数版上(main.py 的 for amount in range(coin, n+1): ways[amount] += ways[amount-coin]),只在 A 的空间压缩基础上叠加二维递推降维与组合计数(外层按面值分组)的额外流程。roj 1294Charm BraceletB 的组合计数沿用 A 教的一维滚动数组状态设计与「枚举方向决定物品能否重复取用」这一步:A 用倒序枚举容量保证每件至多选一次,B 把同一 ways 数组改为按面值外层、金额内层正序枚举(main.py 的 ways[amount] += ways[amount - coin]),使 ways[j-c] 含本轮值从而面值可重复取用,B 原文也直接以 0/1 背包倒序扫描作对照;在此之上 B 才叠加按面值分组去重、max 换成方案数累加与 ways[0]=1 边界。luogu U661986疯狂的背包问题(1) - 01背包问题B 的完全背包计数沿用 A 教的一维滚动数组状态设计并把扫描方向规则翻转使用:A 用倒序让 dp[c-v] 保持上轮值以保证每件物品只选一次,B 在 main.py 里用正序让 ways[j-c] 含本轮值,从而同一面值可重复取用,B 原文也直接以倒序 0/1 背包作对照说明这一分界;在此之上 B 才叠加按面值分组去重、max 换成方案数累加与 ways[0]=1 边界。
题目描述
给定 8x2+2x1 与 2x1+8x2 算同一种,答案保证在 long long 范围内。输入第 1 行是两个整数
text
3 10
1 2 5样例输出为 10。
思路
完全背包计数:设 ways[j] 为凑出金额 ways[0] = 1,外层枚举每种面值 ways[j] += ways[j-c]。外层按面值分组保证同一方案的货币顺序不重复计数(组合而非排列),正序使 ways[j-c] 已含本轮值、同一面值可重复选。
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 04:15
# update_at: 2026-10-01 04:15
import sys
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
v, n = next(data), next(data)
coins = [next(data) for _ in range(v)] # v 个面值,每行一个(token 化后与排版无关)
# 完全背包计数:外层枚举面值,保证"按面值分组",同一面值只能被重复选用,
# 不同面值的使用顺序不产生新方案(即有序和 → 无序组合)。
ways = [0] * (n + 1)
ways[0] = 1 # 金额 0:不取任何货币,恰好一种方案
for coin in coins:
for amount in range(coin, n + 1):
ways[amount] += ways[amount - coin]
print(ways[n])
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 09:59
* update_at: 2026-10-06 09:59
*/
#include <cstdio>
typedef long long ll;
const int MAXN = 10005; // 金额上限 10000
int v, n; // v 种面值,目标金额 n
int coin[30]; // coin[i] 第 i 种货币的面值
ll ways[MAXN]; // ways[j] 表示用已处理过的面值凑出金额 j 的方案数
int main() {
scanf("%d %d", &v, &n);
for (int i = 1; i <= v; ++i)
scanf("%d", &coin[i]);
// 完全背包计数:外层按面值分组,保证同一种方案的货币顺序不重复计数
ways[0] = 1; // 金额 0:什么都不取,恰好一种方案
for (int i = 1; i <= v; ++i)
for (int j = coin[i]; j <= n; ++j) // 金额正序:ways[j-c] 已含本轮值,同一面值可重复选
ways[j] += ways[j - coin[i]];
printf("%lld\n", ways[n]);
return 0;
}