0/1 背包计数:ways[0]=1,ways[s] += ways[s-a[i]] 且容量倒序枚举,答案即 ways[t],O(nt)。
OJ: roj
题目 ID: 1291
难度:普及-
标签:动态规划背包问题计数DP
创建: 2026-09-30 03:41
更新: 2026-10-05 08:10
题目描述
有
思路
设 ways[s] 为从已考虑的数中选出若干、和恰为 ways[0]=1 表示一个数都不选。每个数至多用一次,于是这是 0/1 背包的方案计数版:对每个 ways[s] += ways[s-a[i]],容量必须从 ways[t],复杂度
参考代码
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-09-30 03:41
# update_at: 2026-09-30 03:41
import sys
from itertools import islice
def count_subsets(values: list[int], target: int) -> int:
"""选出若干个数使和恰为 target 的组合数(选哪几个数,不计选取顺序)。
ways[s] 是「已处理的那批数里,和恰为 s 的组合数」。每个数只允许用一次,
所以每轮更新必须写成同时取旧值的 new = old + 错位后的 old:切片右侧在赋值前
已全部求值,zip 读到的旧 ways 不会混入本轮刚写的结果(等价于倒序枚举)。
"""
ways = [1] + [0] * target # ways[0] = 1:一个数都不选,和恰为 0
for value in values:
ways[value:] = [take + skip for take, skip in zip(ways[value:], ways)]
return ways[target]
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
count, target = next(data), next(data) # 题面的 n 与 t
values = list(islice(data, count))
print(count_subsets(values, target))
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-05 08:09
* update_at: 2026-10-05 08:09
*/
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXT = 1005;
int n; // 正整数的个数
int t; // 目标组合和
int a[25]; // 输入的 n 个正整数
ll ways[MAXT]; // ways[s] = 从已考虑的数中选出若干、和恰为 s 的组合数
int main() {
cin >> n >> t;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
// ways[0] = 1:一个数都不选时和恰好为 0,方案数为 1
ways[0] = 1;
for (int i = 1; i <= n; i++) {
// 每个数至多用一次,所以容量必须倒序枚举,读到的是上一轮的旧值
for (int s = t; s >= a[i]; s--) {
ways[s] += ways[s - a[i]];
}
}
cout << ways[t] << endl;
return 0;
}