"如果你觉得你自己还蛮强的,却还是会遇到不会的题,那么这道题很可能就是dp"
点击这里查看原题
用草稿纸写出思路(虽然字可能会有点丑见谅hhh
你可能要点开图片旋转一下
真的很详细很通透很好理解
(因为我从不会到会就是这样的思路引过来的
first
second
third
以下是简短版代码,接着的第二个还会加很长的注释
#include<bits/stdc++.h>
using namespace std;
const int N=20010;
int pre[N],dp[N],p[N],n,m;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int v,w,s;
cin>>v>>w>>s;
memcpy(pre,dp,sizeof(dp));
for(int j=0;j<v;j++){//j是每一类
int head=1,tail=0;
for(int k=j;k<=m;k+=v){//k是每一类中要比较的一组组数
while(head<=tail&&pre[p[tail]]-(p[tail]-j) / v * w <= pre[k] - (k-j) / v * w) tail--;
tail++;
p[tail]=k;
if(head<=tail&&k-p[head]>s*v)head++;
if(head<=tail)dp[k]=max(dp[k],pre[p[head]]+(k-p[head])/v*w);
}
}
}
cout<<dp[m];
}
现在是注释版!!!!
#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) {//分成余数为0~v-1的每一类
int head = 0, tail = -1;//每次循环都重新定义头和尾
for (int k = j; k <= m; k += v) {
//这个k和q[tail]一样表示的都是背包容量,
//从当前位置j开始每次都加v,直到整个背包满了,
//也就是k到了m不能再加一个v了,再加就爆了
if (head <= tail && k - s*v > q[head])++head;
//整个移动框的最大长度是物品的个数乘以物品的体积也就是s*v
//那么你的背包容量范围就不能超过s*v
//也就是说k-q[head]>s*v的话head++整个框要右移
while (head <= tail && pre[q[tail]] - (q[tail] - j)/v * w <= pre[k] - (k - j)/v * w)--tail;
//我要求的是越大越好所以这里用小于号
//如果我变一下题目要求,题目要我从给定的n个负数里面求最小值那么就换汤不
//换药,在细节处的判断符号反过来就好了
if (head <= tail)dp[k] = max(dp[k], pre[q[head]] + (k - q[head])/v * w);
q[++tail] = k;
//每一类的每个未达到最大容量m的k都是要遍历的
//也就是说它们都是一个个需要你塞进去的元素,进而判断队头和队尾的出
}
}
}
cout << dp[m] << endl;
}
这是多重背包Ⅲ,也是最难的一个part,至于多重背包Ⅱ和多重背包Ⅰ就真的是有手就行,这里就不再赘婿了
这里给出我在acwing里面写的题解
多重背包Ⅱ
多重背包Ⅰ
如果你对单调队列理解得比较通透的话就很好理解Ⅲ比Ⅰ多出来的那一坨鬼东西hhh
接下来我们讨论讨论为什么完全背包是正序,而多重背包是逆序:
就是说
对于完全背包,他的j循环是这样的
for(int j=cost; j <money ; j++)
对于多重背包,他的 j 循环却是这样的
for( int j=money ; j>=cost ; j–)
首先,你知道为什么dp[i][j]能从二维压缩到一维吗
就是因为最外面的那个for循环i,他遍历的是一个个物品
遍历了i=1的最佳情况后dp有了一个值
然后遍历i=2的时候有可能会更新一下dp的值
直到i=2, i=3,…,i=n的时候dp更新完毕,这个值就是最大值
好,这个弄懂了我们现在进行第二步
假设一共有两个物品
第一个物品A,体积为1,价值为1,有三个
第二个物品B,体积为2,价值为3,有两个
你有一个容量为7的背包
如果第二个j循环我们按照多重背包的正序而非逆序来处理的话
for(int i=1;i<=n;i++){
int v,w,s;
cin>>v>>w>>s;
for(int j=v;j<=m;j++){
balabala
我把数据流写一下
i=1的时候
v=1,w=1,s=3
dp[1]=1
dp[2]=2
dp[3]=3
dp[4]=3
dp[5]=3
dp[6]=3
dp[7]=3
i=2的时候
v=2,w=3,s=2
dp[1]=1
dp[2]=3
dp[3]=4
dp[4]=6
dp[5]=6
dp[6]=6
dp[7]=6
发现没有,你本来应该要在容量为5的基础上再加一个A物品使得正确答案为dp[6]=8)
dp[7]=7也是错的!dp[7]应该为1×3+3×2=9
原因是i=1第一个物品已经过去了,现在只能讨论i=2第二个物品,你不能在背包容量有剩余的再回去把它加回来
你在背包容量为4的时候因为6>3用两个B把三个A给挤出去了,所以背包容量继续变大的时候你只有两个B,而且已经用完了,从这里开始你的dp值的更新就开始出错了
如果你看完了,请回到 接下来我们讨论讨论为什么完全背包是正序,而多重背包是逆序 这句话开始回过头来再看一遍
为什么完全背包可以正序呢?因为我可以有无限个B
为什么多重背包逆序就能解决问题呢?这个问题交给你萌自己在草稿纸上思考啦
呕心沥血实属不易hhh
(应该都能看懂吧