背包问题:恰好装满

恰好装满背包(Exact-Full Knapsack)是 0/1 背包的一种变形,要求在重量(或体积)恰好等于给定容量 \(W\) 的前提下,优化(或计数、判可行)目标。常见三种场景:

  1. 判可行——问是否存在一种选法使总重恰好 W。

  2. 计方案数——求有多少种选法能恰好装满。

  3. 求最大价值——在恰好装满 \(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)\) 方案。

posted @ 2026-11-11 06:23  slowbyte  阅读(3)  评论(0)    收藏  举报