多重背包问题的二进制优化

我们两清 提交于 2020-02-28 05:08:01

前情提要:动态规划——背包九讲——多重背包问题

多重背包问题

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。

这题目和完全背包问题很类似,特点是:每种物品都有自己特异的件数、花费、价值。

朴素算法:
每种物品有n[i]件可以取用,需要枚举每种物品选了多少件(枚举件数不能超过背包容量)

for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
		for(int k=0;k<=s[i]&&k*v[i]<=j;k++)	//与完全背包相比仅仅是加了一个判断条件而已
			f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);

朴素算法的时间效率很低,通过下面的二进制优化可以把遍历每种物品的n[i]件物品的时间复杂度从O(n)优化为O(log n)
优化:通过展开化简式子不可行,换用二进制优化方式


正篇:背包问题的经典二进制优化

第i种物品是有n[i]件的,这个n[i]可以被展开为n[i]=20+21+…+2k+c (其中2k+1 < n[i] < 2k+2)

在这里插入图片描述

(20+21+…+2k可以表示区间[ 0,2k+1-1 ]的数,通过加上c这个数,能够表示区间[0,n[i]]上的所有数)

同时记录这些加数的值
那这样的话对于[0,n[i]]的每一个数,都可以用这些二进制数表示。即为将n种物品转化成cnt种物品【每种物品的可选择数量都是2的某次方(某种物品的最后一种例外)】

完整代码如下:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 24010, M = 2010;		//数组开的元素个数N --> 2000*log2000 (log2000约等于11.几)
int n, m;
int v[N], w[N];
int f[M];

int main()
{
    cin >> n >> m;
    int cnt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while (k <= s)
        {
            cnt ++ ;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if (s > 0)		//加上最后一位c
        {
            cnt ++ ;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;	//将n种物品转化成cnt种物品【每种物品的可选择数量都是2的某次方(某种物品的最后一种例外)】

    for (int i = 1; i <= n; i ++ )		//此处包含0 1背包问题的优化,可详见我的背包专题
        for (int j = m; j >= v[i]; j -- )
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[m] << endl;
    return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!