多重背包就是背包问题加了个次数每个物品可以选s次,很容易想到的代码就是在状态转换中再加一个循环从0到s,但这样大数据会超时,所以有了很多

优化方案。

  二进制优化:

  每个物品可以选的次数都是不同的,有一种想法是把这可以选s次的物品拆成s个物品,这样就只涉及选与不选,就简化成了01背包问题但还是会超时,因

为拆成了s个但其实没必要我们只需要拆成log2(n)个就可以了,转换成二进制,举个例子,比如说7,7的二进制是111,111以内的数都可以用100,10,1来表示,也就是十进制的1,2,4,但7是特殊情况或者比如说10,同样拆成1、2、4。1、2、4,可以表示7以内的数而我想表示10以内的书就只需要再加个3就可以了,dddd。

  然后这样代码复杂度就不是O(nvs)而是O(nvlog2(s))。

  代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2010;
int f[N],n,m;
struct good
{
int w,v;
};

int main()
{
cin>>n>>m;
vector<good> Good;
good tmp;
for(int i = 1 ; i <= n ; i++ )
{
int v,w,s;
cin>>v>>w>>s;
for(int k = 1 ; k <= s ; k*=2 )
{
s-=k;
Good.push_back({k*w,k*v});
}
if(s>0) Good.push_back({s*w,s*v});
}
for(auto t : Good)
for(int j = m ; j >= t.v ; j--)
f[j] = max(f[j] , f[j-t.v]+t.w );


cout<<f[m]<<endl;
return 0;

}

  单调队列优化:

  这个巨难我看大佬解析看懂的,原解析在这

  作者:lys
  链接:https://www.acwing.com/solution/content/6500/
  来源:AcWing

  首先先从完全背包的思路出发dp[i][j] 表示将前 i 种物品放入容量为 j 的背包中所得到的最大价值dp[i][j] = max(不放入物品 i,放入1个物品 i,放入2个物品 i, … , 放入k个物品 i),这里 k 要满足:k <= s, j - kv >= 0,不放物品 i = dp[i-1][j],放k个物品 i = dp[i-1][j - kv] + kw,dp[i][j] = max(dp[i-1][j], dp[i-1][j-v] + w, dp[i-1][j-2v] + 2w,…, dp[i-1][j-kv] + k*w)。

  然后我们优化成了一维,适当的调整循环条件,我们可以重复利用dp数组来保存上一轮的信息,我们令 dp[j] 表示容量为j的情况下,获得的最大价值,那么,针对每一类物品 i ,我们都更新一下 dp[m] –> dp[0] 的值,最后 dp[m] 就是一个答案。
  dp[m] = max(dp[m], dp[m-v] + w, dp[m-2v] + 2w, dp[m-3v] + 3w, …)
  接下来,我们把 dp[0] –> dp[m] 写成下面这种形式
  dp[0], dp[v], dp[2v], dp[3v], … , dp[kv]
  dp[1], dp[v+1], dp[2
v+1], dp[3v+1], … , dp[kv+1]
  dp[2], dp[v+2], dp[2v+2], dp[3v+2], … , dp[kv+2]
  …
  dp[j], dp[v+j], dp[2
v+j], dp[3v+j], … , dp[kv+j]

  因为kv+j一定可以等于m,所以很容易想到可以把这些分为j个类,每一类中的值,都是在同类之间转换得到的,也就是说,dp[kv+j] 只依赖于 { dp[j], dp[v+j], dp[2v+j], dp[3v+j], … , dp[kv+j] },因为我们需要的是{ dp[j], dp[v+j], dp[2v+j], dp[3v+j], … , dp[kv+j] } 中的最大值,可以通过维护一个单调队列来得到结果。这样的话,问题就变成了 j 个单调队列的问题。所以,我们可以得到:
  dp[j] = dp[j]
  dp[j+v] = max(dp[j] + w, dp[j+v])
  dp[j+2v] = max(dp[j] + 2w, dp[j+v] + w, dp[j+2v])
  dp[j+3v] = max(dp[j] + 3w, dp[j+v] + 2w, dp[j+2v] + w, dp[j+3v])
  …
  但是,这个队列中前面的数,每次都会增加一个 w ,所以我们需要做一些转换
  dp[j] = dp[j]
  dp[j+v] = max(dp[j], dp[j+v] - w) + w
  dp[j+2v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w) + 2w
  dp[j+3v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w, dp[j+3v] - 3w) + 3w
  …
  这样,每次入队的值是 dp[j+kv] - kw

  key:

  维护队列元素的个数,如果不能继续入队,弹出队头元素
  维护队列的单调性,即:尾值 >= dp[j + kv] - kw

  代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <cstring>

using namespace std;

const int N = 20010;

int dp[N], pre[N], q[N];
int n, m;

int main() {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
memcpy(pre, dp, sizeof(dp));
int v, w, s;
cin >> v >> w >> s;
for (int j = 0; j < v; ++j) {
int head = 0, tail = -1;
for (int k = j; k <= m; k += v) {

if (head <= tail && k - s*v > q[head])
++head;

while (head <= tail && pre[q[tail]] - (q[tail] - j)/v * w <= pre[k] - (k - j)/v * w)
--tail;

if (head <= tail)
dp[k] = max(dp[k], pre[q[head]] + (k - q[head])/v * w);

q[++tail] = k;
}
}
}
cout << dp[m] << endl;
return 0;
}