只写了一点点
回顾
01背包
1 | memset(f,0xcf,sizeof f); |
完全背包
1 | memset(f,0xcf,sizeof f); |
多重背包
用二进制优化转换为01背包
1 | void split(Node x){ |
新知
单调队列优化的多重背包
引入
我们考虑多重背包的转移方程(二维),思考,能否像完全背包一样,让 $f[i][j]=max(f[i-1][j],f[i][j-v]+w)$ ,以此来优化呢?
观察:
我们会发现和完全背包相同的是,红色方框内的两个表达式一一对应,相差一个 $w$ ,但是, $f[i][j-v]$ ,有一项没有对应上。
这是为何呢?原因很简单,完全背包 $f[i][j]$ 在不考虑越界的情况下有无穷多种转移情况,在去掉最前端的 $f[i-1][j]$ 后还有无穷多种,恰好可以与 $f[i][j-v]$ 的无穷多种情况一一对应。
但是,变成多重背包后, $f[i][j]$ 只有 $s$ 种,即有限种转移情况,在去掉最前端的 $f[i-1][j]$ 后只有 $s-1$ 种,与 $f[i][j-v]$ 的 $s$ 种一一对应后,还剩一个。
但就是这多余的一个,导致我们无法用 $f[i][j]=max(f[i-1][j],f[i][j-v]+w)$ 来转移,因为多余的一个情况无法预料。
但这仍可以给我们一些启示:
在数轴上标出 $x=j+kv,k \in \mathbb{Z}$ ,即, $x \equiv j \pmod v$ 。
$j$ 是由它前面 $s$ 个点的最大值
转移过来的,即蓝色窗口中标出的点的最大值,而 $j-v$ 也是由其前方 $s$ 个点的最大值转移而来,也就是说,窗口大小不变,窗口在数轴上滑动。
需要注意的是,数轴是无限的,但数组的转移是有边界的,所以我们可以把数轴上无意义的点设为负无穷(当然具体视情况而定) ,这样防止越界。
在解决了以上问题后,我们就只需要找出一种快速求滑动窗口最值的方法即可,而以前学过的单调队列可以在线性的时间内维护该问题。
具体来说,我们把“倒序循环 $j$ ”,改为对每个余数 $u \in [0,v_i-1]$ 倒序循环 $p=\left \lfloor (m-u)/v_i \right \rfloor \sim 0$ ,对应的状态就是 $j=u+p * v_i$ ,由于只能选 $c_i$ 个,故状态转移方程为:
$$f[u+p * v_i]=\max_{p-c_i \le k \le p-1} (f[u+k * v_i]+(p-k) * w_i)$$
我们发现,把 $i,u$ 看做定值,当内层变量 $p$ 减少 $1$ 时,决策 $k$ 的取值范围 $[p-c_i,p-1]$ 均单调减小,所以我们可以维护一个决策点 $k$ 单调递减,数值 $f[u+k * v_i]-k * w_i$ 递减的队列。
例题
代码
1 |
|