多重背包问题究极版(用单调队列优化)+完全背包的正序与多重背包的逆序讨论

"如果你觉得你自己还蛮强的,却还是会遇到不会的题,那么这道题很可能就是dp"

点击这里查看原题
多重背包问题究极版(利用单调队列进行优化)
用草稿纸写出思路(虽然字可能会有点丑见谅hhh
你可能要点开图片旋转一下
真的很详细很通透很好理解
(因为我从不会到会就是这样的思路引过来的
first
假设要分析的第一个物品体积是4
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
(应该都能看懂吧

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值