像潮落潮涌,送我奔向自由。|

寂静的海底

园龄:3年2个月粉丝:57关注:15

📂博客
🔖dp
2023-09-14 21:49阅读: 261评论: 1推荐: 1

扣除某个数的背包

这玩意还是挺常见的都遇到了两三次了还是记一下。

本文的「背包问题」指形如 给出若干二元组 (Ai,vi),求

B(x)=(vi)=x(i=1nAi))

的问题。

比如背包求方案数中 (,)=(+,×),背包求最大价值中 (,)=(max,+)

背包问题,对每个物品求不加入这个物品的贡献的背包答案。

直接枚举物品每次重新背包是 O(n2V) 的,但是这样显然太浪费了。

常见的一些技巧:

维护前后缀——求固定体积

扣掉一个东西的常见做法就是维护前后缀信息,让 fi,j 表示物品 [1,i] 的背包,gi,j 表示物品 [i,n] 的背包,如果要查询背包的单个值 ansc 的话就可以体现出类似 meet-in-the-middle 不用合并全局的优势了,枚举 j,求 j=0x(fi1,jgi+1,xj) 即可,时空复杂度是 O(nV)

维护前后缀——求整个背包数组

求出整个数组对于一般的 (,),当 (,) 卷积可以低于 n2 时才有用,常见的只有 (+,×) 卷积可以用 FFT/NTT 低于 O(n2) 求出 dp 数组。直接使用前缀和后缀的卷积合并,时间复杂度是 O(nVlogV),空间不变。

像什么 (min,+) 卷积能做的话要背包具有凸性,那就根本不用背包。

序列分治

其实对于 i 位置扣掉物品 i 就是对于 [1,i)(i,n] 加入这个物品,可以考虑使用线段树分治或者分治完成。

线段树分治就直接加入 [1,i),(i,n] 这两条线段就行了,然后 dfs 线段树,离开的时候撤回所有操作。

一般分治可以考虑将 [l,r] 分治为 [l,mid],(mid,r],递归左侧的时候加入右侧的物品,递归右侧时加入左侧的物品。

每次背包的变化量都是 O(n) 的,繁琐的撤回不如直接把整个背包数组存下来,然后撤回时直接赋值回操作前的背包数组。

时间复杂度是 O(nVlogn),空间不变,优点是不需要可逆。

背包回退

回退背包好像一般仅限于背包操作是 (+,×) 这种求方案数的,无序的,线性的,有可减性的,可逆的(本质即对背包操作的多项式的常数项非 0 或矩阵满秩)。

考虑背包更新是无序的,先加入拿个物品是一样的,所以我们先求出一个完整的背包,然后扣掉这个物品,因为无序性我们可以假装这个物品是最后加入的,然后将它对背包的更新做一遍逆操作。

例:01 背包的操作

for(int i = V ; i >= 0 ; -- i)
	f[i] += f[i-a];

的逆操作就是

for(int i = 0 ; i <= V ; ++ i)
	f[i] -= f[i-a];

循环的顺序改变是因为本身我们的(01)背包是要用 没更新的数 去更新数,所以现在要用 已经还原的数 去还原待还原的数。

如果方案数很大,维护可行性的话可以使用多哈希取模的方法,这种特殊的问题用自然溢出就是找死。

小逝牛刀

消失之物

板子,求整个 dp 数组,求方案数。

建议都写写看。

CF303E

基本 dp 解法在这里,是个很套路的题,其实你不会也不影响你学习这个背包回退的技巧,如果没兴趣不妨直接往下看。

我们现在要求一个概率的二维背包(维护实数),其转移形式为

fi,j=fi,j×ax+fi,j1×bx+fi1,j×cx

其中满足 ax,bx,cx[0,1],ax+bx+cx=1(因为是概率)。

要分别求扣掉每个 x 的贡献后的背包。

直接做可以做到 O(n2V2) 求出所有方法,可以通过。

各种做法:

维护前后缀:因为要求整个数组,所以需要合并一个二维背包。使用二维 FFT,复杂度 O(nV2logV),常数大。

分治:也是类似的分治,可以在每层把操作前的 f 数组存下来直接回到操作前,而不用撤回,复杂度 O(nV2logn),常数小。

回退:

考虑我们的操作:

for(int i = c ; i >= 0 ; -- i) {
	for(int j = c ; j >= 0 ; -- j) {
		f[i][j] *= a;
		if(j > 0) f[i][j] += f[i][j - 1] * b ;
		if(i > 0) f[i][j] += f[i - 1][j] * c ;
	}
}

对它进行逆操作

for(int i = 0 ; i <= c ; ++ i) {
	for(int j = 0 ; j <= c ; ++ j) {
		if(j > 0) f[i][j] -= f[i][j - 1] * b ;
		if(i > 0) f[i][j] -= f[i - 1][j] * c ;
		f[i][j] /= a;
	}
}

但是现在就产生了一个问题:当被回退的数的 a=0 时,就无法进行回退了,因为 ×0 并不是一个单一映射,它是不可逆的。

考虑 a+b+c=1,所以 max(a,b,c)13,用三者中的非零值来解方程即可,为精度着想当然是取最大值,这里举例用 b

fi,j=afi,j+bfi,j1+cfi1,jfi,j1=fi,j+afi,j+cfi1,jb

这样就得从右往左更新,如果选择 c 就要从下往上更新。

复杂度 O(nV2),比分治快。

posted @   寂静的海底  阅读(261)  评论(1编辑  收藏  举报
历史上的今天:
2022-09-14 GYM103861F 解题报告
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起