背包DP(2)

背包DP(2)

1.多重背包

题目:n种物品,m的背包。第i种物品,最多有s[i]件,每件体积v[i],价格是w[i]。求解选择哪些物品放入背包,不超过背包,价值最大。输出最大价值。

(1)朴素算法

不如这样想,取s[i]件物品,就是s[i]件一样的物品。即转换为01背包。
令k1为s[i]的求和
时间复杂度就是 O (m*k1)。

(2)二进制优化

和快速幂的思想相同,任意一个整数可以变成若干个 2k相加,这实际上就是说,是个十进制数可以变成二进制数。

把第i个物品拆分成若干件物品,每件物品的v与w乘上一个系数 (20,21,22.....,2k,s[i]-2k+1)这样也可以转换为01背包,但相较与之前的有了很大的优化。
例如:
s[i] = 12,系数为 1,2,4,5
在之前就是12件物品,现在就是4件物品。
这四件物品是:
(v[i],w[i]),(2v[i],2w[i]),(4v[i],4w[i]),(5v[i],5w[i])
令k2为logs[i]的求和
时间复杂度为 O(m*k2).

(3)单调队列优化

如果我们使用一维数组f[],那么f[]是怎样更新的呢?
打表可以发现。每一次更新,就代表着选择了一次v[i],把这样的看作一个类
f数组是按照类更新的。把f[]按照v[i]的余数拆分为v[i]个类
(下面简写为v)
eg:
f[0],f[v],f[2v]....f[kv]
f[1],f[1+v],f[1+2v].....f[1+kv]
f[j],f[j+v], f[j+2v].....f[j+kv]
其中0~j都是v的余数。

f[j]是由前面不超过s[i]的同类值递推得到的。这就相当于从前面宽度为s[i]的窗口来挑选最大值更新当前值。这样就可以使用单调队列来维护。从而f[j]的更新次数变为1次。
因为一维的滚动数组是没办法储存路径的。
准备一个数组g,用于备份.

for(int i = 1;i <= n;i++){  
	    memcpy(g,f,sizeof f);  
	    for(int j = 0;j < v[i];j++){  
                    int head = 0,tail = -1;  
		    for(int k = j;k <= m;k+=v[i]){  
                          //q[h]不在窗口[k-s[i]*v[i],k-v[i]]内,队头出队  
		          if(head <= tail && q[head] < k-s[i]*v[i]) 
		                 head++;  
		          //队头为最大值  
		          if(head <= tail) 
			         f[k] = max(g[k],g[q[head]]+(k-q[head])/v[i]*w[i]);  
                          //维护队列单调性
                          while(head <= tail && g[k] >= g[q[tail]]+(k-q[tail])/v[i]*w[i])  
                                 tail--;  
                          q[++tail] = k;
                     }  
            }  
}

时间复杂度为O(n*m);

posted @ 2021-01-25 00:34  Paranoid5  阅读(62)  评论(0编辑  收藏  举报