可行性判定
当前有 N 件物品和一个容积为 V 的背包。
已知第 i 件物品的体积是 ci,每种物品有且仅有一件,每一件物品能够选择放或者不放入背包。
现在我们不考虑物品的价值,只关心是否能够取出若干个物品,恰好使这个背包被装满。
也就是说,现在我们需要选出若干件物品,使它们的体积之和 恰好为 V 。
dp 数组初始化
普通的 01 背包中要求放入物品的体积之和不超过 V,而现在我们需要使体积之和恰好为 V。
因为结果只需要输出 V 这个体积是否能被组出,因此我们可以将 dp 数组定义成bool
类型。
- 令 dp[i][j] 表示前 i 个物品中取出若干个物品体积之和为 j 的可行性,当可行的时候 dp[i][j] 等于 11,否则等于 00。
- 在最开始背包里空空如也的时候,背包里物品的体积之和(没有物品)为 00,所以 00 这个状态在开始就是合法的。
- 当要求选出物品体积之和恰好为 V 的条件下,唯一可行的转移路线是从 dp[0][0]→dp[N][V],即dp[0][0] 是唯一可行的起点。
- 所以应当先将整个
dp
数组初始化为 00 ,再将 [0][0]dp[0][0] 修改为 11 。
核心代码
dp[0][0] = 1; // 初始化开始时唯一的合法状态
for (int i = 1; i <= N; i++) {for (int j = 0; j <= V; j++) {if (j < c[i]) { // 如果容积小于当前物品的体积dp[i][j] = dp[i - 1][j]; // 直接从上一层转移} else if (dp[i - 1][j] || dp[i - 1][j - c[i]]){// 如果前 i - 1 个物品已经能凑出体积之和为 j// 或者前 i - 1 个物品已经能凑出体积之和为 j - c[i] , 那个当前物品加入可以使体积之和变为 jdp[i][j] = 1; // 此时前 i 个物品能够凑出体积之和为 j}}
}
时间复杂度为 O(NV),空间复杂度为 O(NV)。
空间优化:
dp[0] = 1;
for (int i = 1; i <= N; i++) {for (int j = V; j >= c[i]; j--) {dp[j] |= dp[j - c[i]];}
}
完整代码:
#include <iostream>
using namespace std;bool dp[110][1010];
int c[110];int main() {int N, V;cin >> N >> V;for (int i = 1; i <= N; i++) {cin >> c[i];}dp[0][0] = 1;for(int i = 1; i <= N; i++){for(int j = 0; j <= V; j++){if (j < c[i]){dp[i][j] = dp[i - 1][j];} else if (dp[i - 1][j] || dp[i - 1][j - c[i]]){dp[i][j] = 1;}}}if (dp[N][V]){cout << "Yes" << endl;} else {cout << "No" << endl;}return 0;
}