使用bitset优化0/1背包的时空复杂度。(100320. 执行操作可获得的最大总奖励 II)

被leetcode401单周赛狠狠打败了。就此记录下bitset优化0-1背包。利用数字的二进制操作对整体进行转移

问题描述

点击链接直接获取问题描述

求解思路

状态定义

定义\(dp[i][j]\)表示能否从前i个数中得到总奖励j。每次转移对前一个状态的j有特殊要求。

状态转移方程

考虑第i个物品选或不选:

  • 不选第i个物品:\(dp[i][j] = dp[i-1][j]\)
  • 选择第i个物品:\(dp[i][j] = dp[i-1][j-w[i]]\) ,需要满足\(j \geq w[i]\)\(j - w[i] < w[i]\)
    状态为能否从前i个数中得到奖励,即$$dp[i][j] = dp[i-1][j]\ \cup\ dp[i-1][j-w[i]] $$
初始化方式

初始值\(dp[0][0] = true\)
所求答案为\(dp[n][j] = true\)中最大的j。

优化方式

对应的状态个数为\(n^{2}\),当然是过不了当前\(n = 2 \times 10^{5}\) 的数据。需要优化掉其中一个维度。

  1. 由于状态转移中第\(i\)层一定从第\(i - 1\)层转移过来,可以两个数组或者原地滚动进行优化。但只省下空间,总状态数还是那么多,没啥用

  2. 注意到\(dp[i][j]\)一定是从\(dp[i-1][j]\)\(dp[i-1][j-w[i]]\)转移过来,对应的\(0 \le j-w[i] < w[i]\)。也就是存在一一对应的关系,并且转移方式为两者取或。使用bitset对优化当前0/1背包的状态转移。

    二进制数表示每个状态,二进制数位j上的数字表示当前状态下的j能否被取到。 对应的状态转移为了满足限制条件,需要先将\(x\)中数位大于\(j\)的位置设置为0,再将当前的二进制数\(x\)与左移\(w[i]\)位的\(x^{*}\)取或。初始状态设置为1,代表开始只有0满足要求。

bitset介绍

该部分内容搬运自 -Wallace-的文章

它类似于 bool 数组,每个位置只有两种值:0 或 1。
bitset 的实现方式是压位,那么一个大小为 n𝑛 的 bitset 的空间复杂度为 \(O(\frac{1}{ω}n)\)。其中 ω=32 或 64(系统位数)。

基本操作
bitset<N> f;//定义一个大小为N的bitset,下标范围[0, N)
f.set(i);//在下标i设为1
f.reset(i);//将下标i设置为0
f.test(i);//判断下标i处是否为1
f[i];//取值

除了构造函数,其余操作均为\(O(1)\)

进阶操作
f.set(); // 全部置为 1 
f.reset(); // 全部置为 0 
f = g; // bitset 赋值 
f &= g; // 将 f 对 g 做按位与操作 
f |= g; // 将 f 对 g 做按位或操作 
f ^= g; // 将 f 对 g 做按位异或操作 
// 以及各种位运算操作 
f.count(); // 计算 bitset 中 1 的个数

这些范围操作都是\(O(\frac{1}{ω}n)\)的时间复杂度。bitset时空复杂度中的常数\(\frac{1}{w}\) 是bitset优化的关键。

代码案例

源自灵茶山艾府

bitset<100000>f{1};//初始化下标0的位置为1
for (int v : rewardValues) {
    int shift = f.size() - v;
    //bitset的区间操作带有常数1/w
	f |= f << shift >> shift << v;
}
for (int i = rewardValues.back() * 2 - 1; ; i--) {
		//获取最大的下标为1的位置
        if (f.test(i)) {
            return i;
        }
    }

使用bitset优化后,对应的时间复杂度为\((\frac{1}{w} n^{2})\) ,\(w\)对应为32或64,满足数据范围\(n = 2\times 10^{5}\)的要求。

posted @ 2024-06-10 00:20  tanch25  阅读(29)  评论(0编辑  收藏  举报