先求出全部物品的方案数f[j],再对每个物品i用g[j]=f[j]-g[j-w[i]]推出不含i的方案数。
OJ: luogu
题目 ID: P4141
难度:普及+/提高-
标签:动态规划01背包计数补集
日期: 2026-08-08 23:13
题意
有
思路
一句话本质:如果没有物品
直接对每个
每个
全体物品的方案数
先跑一遍包含全部
如何从
对于固定体积
"含物品
任何一个含
设
当
为什么这个递推是安全的?
关键在于
注意取模只需末位数字,所以用 %10 即可(减法时加 10 再取模防负数)。
最终对每个
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
int n, m;
int w[MAXN]; // 物品重量(体积)
// f[j] 表示用所有物品凑出和为 j 的方案数。
int f[MAXN];
// g[j] 表示不含当前物品时,凑出和为 j 的方案数。
int g[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> w[i];
}
// 第一遍:正序 DP 求出包含所有物品的方案数 f[]。
f[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = m; j >= w[i]; j--) {
f[j] = (f[j] + f[j - w[i]]) % 10;
}
}
// 对每个物品 i,用 f[] - (包含 i 的方案) 得到不含 i 的方案数。
for (int i = 1; i <= n; i++) {
g[0] = 1;
for (int j = 1; j <= m; j++) {
if (j < w[i]) {
g[j] = f[j];
} else {
// f[j] 中可能包含选了物品 i 的方案(即 g[j - w[i]]),
// 从 f[j] 中减去这些方案得到不含 i 的方案数。
g[j] = (f[j] - g[j - w[i]] + 10) % 10;
}
}
for (int j = 1; j <= m; j++) {
cout << g[j];
}
cout << '\n';
}
return 0;
}复杂度
- 全物品 DP:
- 每个
的 推出: ,共 - 总时间复杂度:
, 没有问题 - 空间复杂度:
总结
这题的明星技巧是"从全体方案中扣除某个物品的贡献"。它利用方案数在加/减物品时的对称关系,把
