疯狂的背包问题(5) - 完全背包问题(可行性问题)

使用完全背包DP判断容量V是否可达,每种物品无限件可用,dp[c]记录容量c可否凑出,容量正序枚举。

OJ: luogu

题目 ID: U663703

难度:普及-

标签:动态规划完全背包背包

日期: 2026-08-08 23:11

题意

N 种物品,每种无限件,每件体积 v_i。问是否存在一种选法使总体积恰好等于背包容量 V。

思路

一句话本质:每种物品可以选任意多件,判断能否恰好凑出容量 V。

brute.py 的瓶颈在哪?

brute.py 对每种物品枚举选取数量 0 到 max_k 件,dfs 递归搜索。每种物品的 k 可以取很多值,搜索树分支巨大。N=1000 时指数级,不可行。

这道题和 01 背包可行性问题(U663295)差别在哪里?

01 背包每件只能用一次,容量倒序。这里每件可以用无限次,容量正序。除此之外,状态定义和转移思路完全一样——都是布尔可达性。

正序为什么能实现无限次使用?

正序枚举时,处理到容量 c 时 dp[c - v] 可能已经被当前物品更新过(即当前物品已被用来凑过 c-v),再从 c-v 转移到 c 就等于"又用了一次当前物品"。反复如此,物品就可以被反复使用,实现了无限件。

状态定义和转移是什么?

定义 dp[c] 为布尔值,表示容量 c 能否被恰好凑出。

初始 dp[0] = true(不选任何物品时容量为 0)。

对于每件物品体积 v,容量 c 从 v 到 V 正序:

text
dp[c] = dp[c] || dp[c - v]

代码

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-08-08 23:11
 * update_at: 2026-08-08 23:11
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int N, V;
    cin >> N >> V;
    vector<bool> dp(V + 1);
    dp[0] = true;
    for (int i = 0; i < N; ++i) {
        int v;
        cin >> v;
        for (int c = v; c <= V; ++c)
            dp[c] = dp[c] || dp[c - v];
    }
    cout << (dp[V] ? "true" : "false") << '\n';
    return 0;
}

复杂度

  • 时间:O(N × V),每种物品遍历 V 个容量
  • 空间:O(V),dp 数组大小 V+1

总结

完全背包可行性与 01 背包可行性的区别只有容量枚举方向:正序 vs 倒序。dp[0] = true 是唯一的基础可达状态,所有其他容量通过 dp[c] |= dp[c - v] 从更小的容量推导而来。