背包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);