背包九讲之三:多重背包问题:一个物品允许选有限次

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
多重背包问题只是相当于将完全背包问题中的物品可用次数由无限变为了有限。所以最简单的办法就是将未优化前的完全背包问题算法第三重循环修改一下:
for (int k = 1; k <= s[i] && k <= j / v[i]; ++k)
1、二进制优化方法,时间复杂度:o(N*logS*V)
 1 #include<iostream>
 2 #include<vector>
 3 #include<algorithm>
 4 using namespace std;
 5 struct Good {
 6     int vi, wi;
 7 };
 8 const int array_size = 2001;
 9 int f[array_size], N, V, v, w, s;
10 vector<Good> vessel;
11 /*
12 二进制优化方法
13 v,w,s
14 每个物品拆成s份,最多需要log(s)个数来表示
15 例:某物品共7个,可由1,2,4(pow(2,k))组合
16 分别把1个物品,2个物品,4个物品当作1组,
17 即将7个物品分为3个物品,其体积与价值分别为:
18 v,w;2*v,2*w;4*v,4*w。转化为01背包问题。
19 */
20 int main() {
21     cin >> N >> V;
22     for (int i = 0; i < N; ++i) {
23         cin >> v >> w >> s;
24         for (int k = 1; k <= s; k *= 2) {
25             s -= k;
26             vessel.push_back({ k * v,k * w });
27         }
28         if (s > 0)
29             vessel.push_back({ s * v,s * w });
30     }
31     //01背包问题算法
32     for (Good good : vessel)
33         for (int j = V; j >= good.vi; --j)
34             f[j] = max(f[j], f[j - good.vi] + good.wi);
35     cout << f[V];
36 }
2、单调队列优化方法
注:单调队列:得到当前序列长度为"m"的子序列中的最小值或最大值。单调队列只有递增和递减两种队列。单调队列push进去的是原序列的下标而非值。
当push一个下标"i"时:
(1)当deque.front()<=i-m时,pop之;
(2)当f[deque.back()]>f[i]时,pop之,再大再pop之,不大为止;
(3)push下标"i"。
最后deque.front()就是当前序列长度为"m"的子序列中的最小值。
试例:
10 100
8 7 1
2 2 5
2 2 3
8 6 250
10 7 200
5 2 400
2 1 948
1 1 1
7 4 285
1 1 3
#include<iostream>
#include<algorithm>
using namespace std;
/*
f:前i个物品,背包容积0-V,对应的最大价值
g:前i-1个物品,背包容积0-V,对应的最大价值
*/
const int array_size = 20001;
int f[array_size], g[array_size], q[array_size], N, V, v, w, s;
int main() {
    cin >> N >> V;
    for (int i = 0; i < N; ++i) {
        cin >> v >> w >> s;
        copy(f, f + array_size, g);
        for (int j = 0; j < v; ++j) {
            int hh = 0, tt = -1;
            for (int k = j; k <= V; k += v) {
                f[k] = g[k];
                if (hh <= tt && k - s * v > q[hh])
                    ++hh; //取出队首元素,队列首部当前子序列范围内一定是最大值的下标
                if (hh <= tt)
                    f[k] = max(f[k], g[q[hh]] + (k - q[hh]) / v * w); //用最大值更新一下f[k]
                while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w)
                    --tt; //剔除队列中一定不会用到的元素
                q[++tt] = k; //push新值的下标
            }
        }
    }
    cout << f[V];
}
const int array_size = 20001;
int f[array_size], g[array_size], N, V, v, w, s;
deque<int> q;
int main() {
    cin >> N >> V;
    for (int i = 0; i < N; ++i) {
        cin >> v >> w >> s;
        copy(f, f + array_size, g);
        for (int j = 0; j < v; ++j) {
            for (int k = j; k <= V; k += v) {
                f[k] = g[k];
                if (!q.empty() && k - s * v > q.front())
                    q.pop_front();//取出队首元素,队列首部当前子序列范围内一定是最大值的下标
                if (!q.empty())
                    f[k] = max(f[k], g[q.front()] + (k - q.front()) / v * w); //用最大值更新一下f[k]
                while (!q.empty() && g[q.back()] - (q.back() - j) / v * w <= g[k] - (k - j) / v * w)
                    q.pop_back(); //剔除队列中一定不会用到的元素
                q.push_back(k); //push新值的下标
            }
        }
    }
    cout << f[V];
}

猜你喜欢

转载自www.cnblogs.com/xiehuazhen/p/12464870.html