【ACWing】5. 多重背包问题 II

题目地址:

https://www.acwing.com/problem/content/5/

N N N种物品和一个容量是 V V V的背包。第 i i i种物品最多有 s i s_i si件,每件体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

数据范围:
0 < N ≤ 1000 0< N\le 1000 0<N1000
0 < V ≤ 2000 0< V\le 2000 0<V2000
0 < v i , w i , s i ≤ 2000 0<v_i,w_i,s_i\le 2000 0<vi,wi,si2000

思路是动态规划。但这里需要用二进制优化,其基本思路是,将每个物品的个数 s s s拆成若干个形如 2 k 2^k 2k的个数的”打包”,这样就将多重背包问题转为了 0 − 1 0-1 01背包问题了,从而将时间复杂度降为 O ( V ∑ i = 1 N log ⁡ s i ) O(V\sum_{i=1}^{N} \log s_i) O(Vi=1Nlogsi)。例如,如果一个物品有 10 10 10个,那么可以将它拆成 1 + 2 + 2 2 + 3 1+2+2^2+3 1+2+22+3,这样这个物品的所有取法,一定包含于将其拆成若干小块,每个小块最多取一个的取法。其正确性证明参考https://blog.csdn.net/qq_46105170/article/details/104097159。代码如下:

#include <iostream>
using namespace std;

const int N = 25000, M = 2010;

int n, m;
int v[N], w[N];
int f[N];

int main() {
    
    
    cin >> n >> m;

    int cnt = 0;
    for (int i = 1; i <= n; i++) {
    
    
        int a, b, s;
        // a是价值,b是重量,s是个数
        cin >> a >> b >> s;
        // 下面将s拆分成2的幂次
        int k = 1;
        while (k <= s) {
    
    
            cnt++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k <<= 1;
        }
		
		// 如果没拆干净,把剩余的部分也打包
        if (s > 0) {
    
    
            cnt++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
	
	// 拆完后新的个数重新赋值,然后做0-1背包
    n = cnt;
    for (int i = 1; i <= n; i++)
        for (int j = m; j >= 0; j--)
            if (j >= v[i])
                f[j] = max(f[j], f[j - v[i]] + w[i]); 

    cout << f[m] << endl;

    return 0;
}

时间复杂度 O ( ( ∑ i = 1 N log ⁡ s i ) V ) O((\sum_{i=1}^{N} \log s_i) V) O((i=1Nlogsi)V),空间 O ( V ) O(V) O(V)

代码里 N N N 25000 25000 25000 1000 × log ⁡ 2000 1000\times \log 2000 1000×log2000算出来的。

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/113840962