背包问题:恰好装满
恰好装满背包(Exact-Full Knapsack)是 0/1 背包的一种变形,要求在重量(或体积)恰好等于给定容量 \(W\) 的前提下,优化(或计数、判可行)目标。常见三种场景:
-
判可行——问是否存在一种选法使总重恰好 W。
-
计方案数——求有多少种选法能恰好装满。
-
求最大价值——在恰好装满 \(W\) 的前提下,使价值之和最大。
1D 动态规划思路
设有 n 件物品,第 i 件重量 \(w_i\),价值 \(v_i\)(计数/可行时 \(v_i\) 可忽略):
-
定义状态 \(dp[j]\):含义因场景而异。
-
初始化:
-
判可行:
dp[0]=true, 其余dp[j]=false。 -
计方案数:
dp[0]=1, 其余dp[j]=0。 -
最大价值:
dp[0]=0, 其余dp[j]= -∞(用很小的负数或-1e18表示不可达)。
-
-
转移(遍历每件物品,逆序枚举容量):
for(int i=1;i<=n;i++){ for(int j=W;j>=w[i];j--){ // 可行性: dp[j] = dp[j] | dp[j - w[i]]; // 方案数(若答案可能很大,要取模): dp[j] = dp[j] + dp[j - w[i]]; // 最大价值: if(dp[j - w[i]] != -INF) dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } } -
最终答案都在
dp[W]:-
可行则
dp[W]==true; -
方案数即
dp[W]; -
最大价值若仍
<0(或-INF)则表示无解,否则即dp[W]。
-
时间复杂度都是 \(O(nW)\),空间 \(O(W)\)。
核心 C++ 模板
下面给出“恰好装满求最大价值”版本。其他两种只需改初始值和转移中的操作即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll NEG_INF = LLONG_MIN / 4;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, W;
cin >> n >> W;
vector<int> w(n+1);
vector<ll> v(n+1);
for(int i=1; i<=n; i++){
cin >> w[i] >> v[i];
}
// dp[j] = max value to get exactly weight j
vector<ll> dp(W+1, NEG_INF);
dp[0] = 0;
for(int i=1; i<=n; i++){
for(int j=W; j>=w[i]; j--){
if(dp[j - w[i]] != NEG_INF){
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
}
if(dp[W] < 0){
cout << "Impossible\n";
} else {
cout << dp[W] << "\n";
}
return 0;
}
-
如果只需 判可行,令
vector<char> dp(W+1); dp[0]=1;,转移中用dp[j] |= dp[j-w[i]];,最后检查dp[W]。 -
如果要 计方案数,令
vector<int> dp(W+1); dp[0]=1;,转移中用dp[j] = (dp[j] + dp[j-w[i]]) % MOD;。
常见优化
-
位运算加速判可行:用 C++
bitset,一次转移为bs |= (bs << w[i]);,速度快且写法简洁。 -
剪枝:当 \(W\) 很大时,可尝试分组计数、分块折半等高级技巧,但最常用的还是上述 \(O(nW)\) 方案。

浙公网安备 33010602011771号