AcWing 5 多重背包问题 II

题目描述:

有 N 种物品和一个容量是 V的背包。第 i种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N≤1000
0<V≤2000
0<vi,wi,si≤2000

提示:

本题考查多重背包的二进制优化方法。

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

分析:

AcWing 4 多重背包问题 I中我们介绍了多重背包问题的常规解法,本题我们将使用二进制优化来解决多重背包问题。多重背包问题的状态转移方程是f[i][j] = max{f[i-1][j-k*v[i]] + k*w[i]},因为每个状态都要去比较s个状态的大小才能得出,所以复杂度为O(nms),在本题的数据范围内最大运算次数是4*10^9级别的,显然会TLE。不使用单调队列优化的话,我们也可以通过降低求解每个状态需要的比较次数来降低时间复杂度。

回忆下01背包问题,n个物品排成一排,每个物品要么选要么不选,选也只能选一次。多重背包问题的不同之处在于第i个物品有s[i]个,试想下同样排成一排,比如某件物品有s件,我们可以理解为第一件该物品选不选,第二件选不选......。这就完成转化为了01背包问题了,只要将同种物品看作不同的物品即可。但是直接这样做时间复杂度并没有优化。我们对多重背包问题的状态进行划分,第i个物品可以选0,1,2,...,s[i]次,如果我们将这s个物品分成h组,合理的设置每组数量,就可以通过将s件物品的每个物品选与不选的问题转化为h组物品每组物品的选与不选问题了。举个例子,s = 3,我们原本需要考虑第一件选不选,第二件选不选,第三件选不选;现在将其分成两组,第一组1件,第二组2件,00-两组物品都不选,01-选第一组不选第二组,10-选第二组不选第一组,11-两组都选,这四种情况分别对应了该间物品的四种选法,就像计算机组成原理里面的片选线一样。我们知道,一个十进制数都可以用二进制数来表示,同样我们可以对s进行二进制分组,即第1组1个,第2组2个,...,第i组2^i个。这里涉及到两种分法,第一种:5 = 4 + 1;第二种:5 = 1 + 2 + 2。我们这里选择第二种分法,因为如果严格按照二进制来分,我们需要先判断不超过s的最大2^k是多少,然后每组的数量都要判断下,即执行log运算,比如5,我们先log5取整得到2,第一组2^2件,第二组1件,而第二种分法类似于完全二叉树,不需要做log运算,只要s大于1,第一组就分1件,然后看剩下的值大小,比如5分到第一组1件,还剩4,分到第二组2件,现在还剩2件,要小于4件,故剩下的单独一组。1 + 2 + 4 + 8 + ... + 2^(k-1) = 2^k - 1,即前k - 1组可以表示出0到2^k - 1的数,再加上剩下的一组r件,可以表示出0到2^k - 1 + r中所有的数。举个例子,一个物品体积为1,价值为1,现在有5件,我们分三组,按照之前的分法,分别是1,2,2。现在我们将各组物品看作是新的物品,也就是第一件物品体积为1,价值为1;第二件体积为2,价值为2,;第三件体积为2,价值为2.这样s个同种物品就转化为了logs种不同物品了,接着调用01背包的算法即可解决。

复杂度分析:多重背包问题的二进制优化将物品的种类数n扩大到nlogs,背包容量还是m,故时间复杂度为mnlogs = 2*10^6 * log2000 = 2.2 * 10 ^7,可以在一秒内完成运算。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 11010,M = 2010;
int n,m,v[N],w[N],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){//将s件物品分组存储
            cnt++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if(s > 0){//如果s没被分完,则剩下的单独一组
            cnt++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    for(int i = 1;i <= n;i++)
        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;
}
发布了272 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/103863340