多重背包&二进制优化&单调队列优化

参考【真的很详细\rightarrow】:多重背包问题大全(超详细)_曼切斯特的流氓的博客 

普通的多重背包

三重循环复杂度;

【注意:这是把多重背包拆分成 01背包 所以在用一维dp数组的时候,体积记得要倒着来!!!

【注意这里是外层枚举体积,内层枚举物品个数】

4. 多重背包问题 I - AcWing题库

#include<bits/stdc++.h>
using namespace std;
int dp[120];
int s,v,w;
int main()
{
    int m,n;cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>v>>w>>s;
        for(int j=m;j>0;j--){
            for(int k=1;k<=s&&j-v*k>=0;k++){
                dp[j]=max(dp[j],dp[j-v*k]+w*k);
            }
        }
    }
    cout<<dp[m];
    return 0;
}

二进制优化多重背包

通过二进制来代表拿的物品数,用二进制将同种物品 分别 合并;

【注意:外层枚举新合成出来的物品,内层枚举体积】【注意体积要倒着来,就像01背包一样】

5. 多重背包问题 II - AcWing题库

#include <iostream>
using namespace std;
int n,m,v[15],w[15],dp[2010];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int cnt=0;int tiji,jia,s;
        cin>>tiji>>jia>>s;
        for(int k=1; k<=s; k<<=1){
            v[++cnt]=k*tiji;w[cnt]=k*jia;
            s-=k;
        }
        if(s){v[++cnt]=s*tiji;w[cnt]=s*jia;}
        for(int k=1;k<=cnt;k++){
            for(int j=m;j-v[k]>=0;j--){
               dp[j]=max(dp[j],dp[j-v[k]]+w[k]);
            }
         } 
    }
    cout<<dp[m];return 0;
}

单调队列优化 

数组来充当双端队列,stl会TLE;

当前为v,w,s分别为体积价值件数,

Q1:当前的体积是k,那么这个dp[k]是由什么更新的?(这里的k不等同于代码里的k)

——是由dp[k-s*v]……dp[k-2*v]、dp[k-v]、dp[k]一步步推来;

Q2:那么有多少种dp[k-s*v]……dp[k-2*v]、dp[k-v]、dp[k]序列呢?

——有0,1,…… v-2,v-1,共 v 种

(因为0和v是在同一个序列中,1和v+1在同一个序列中……)

1. 代码中的 j 是序列的种数,范围是【0,v-1】;(同一个i里,不同种的序列不会相互影响)

2. 第 j 种序列里的 第k个体积量,若双端队列里的头是小于 k-s*v ,则它对k就没有影响了,出队;

3. 维护单调队列:

 q[tail] 是第j种序列中的一员,q[tail]的“潜力” g[q[tail]](上一个状态)上能塞下的物品数量(k-q[tail])/v 乘以每件的单价w;

g[k]g[q[tail]]+(k-q[tail])/v*w比大小,若g[k]大,q[tail]]弹出,于是队列里就是从队头到队尾,“潜力”从大到小的排序

4.  动态转移方程:

因为本来是要像01背包一样倒着来的,但是单调队列没法倒着(指我不会),所以这里要么 二维数组 要么 两个一维数组(当前状态和上一个状态),这里g是上一个状态,dp是当前状态;

当前量dp[k]的值要么是 上一个状态g[k]的值 ,要么是 单调队列中的队首的“潜力”,在这里两个值中选最大值;

【注意⚠】

1. memcpy函数的用法  memcpy_百度百科 (baidu.com)

 2. 下面的代码head和tail是指向变量本身而非下一个位置,所以注意入队的时候是++tail;

3.注意单调队列维护的时候有2个条件:队列不为空g[k]大于队尾的“潜力”;

#include<bits/stdc++.h>
using namespace std;
int dp[20005],g[20005],q[20005];
int main()
{
    int n,m;cin>>n>>m;
    for(int i=0;i<n;i++){
        int v,w,s;cin>>v>>w>>s;
        memcpy(g,dp,sizeof(g));
        for(int j=0;j<v;j++){
            int head=0,tail=-1;//初始化注意
            for(int k=j;k<=m;k+=v){
                if(head<=tail){if(q[head]<k-s*v)head++;}//队头弹出
                while(head<=tail&&g[k]>=g[q[tail]]+(k-q[tail])/v*w){tail--;}//队尾弹出
                q[++tail]=k;//入队
                dp[k]=max(g[k],g[q[head]]+(k-q[head])/v*w);//状态转移方程
            }
        }
    }
    cout<<dp[m];
    return 0;
}

   

单调队列用stl的话会TLE,甚至比二进制优化还要慢,清空的时候哪怕是新建队列也会T,如果有人知道为什么的话求评论解答;

会被TLE的代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;const int maxn=20010;int f[maxn],g[maxn];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        memcpy(g,f,sizeof(f));
        int v,w,s;scanf("%d%d%d",&v,&w,&s);
        for(int j=0;j<v;j++){
            deque<int>q;
            for(int k=j;k<=m;k+=v){
                if(!q.empty()&&k-s*v>q.front()) q.pop_front();
                if(!q.empty()) f[k]=max(g[k],g[q.front()]+(k-q.front())/v*w);
                while(!q.empty()&&g[k]>=g[q.back()]+(k-q.back())/v*w) q.pop_back();
                q.push_back(k);
            }
        }
    }
    printf("%d\n",f[m]);
    return 0;
}

混合背包例题(做个小测试)   7. 混合背包问题 - AcWing题库

代码 :

#include<bits/stdc++.h>
using namespace std;
int vv[20],ww[20];
int dp[1005];
int main()
{
    int n,m;cin>>n>>m;
    for(int i=0;i<n;i++){
        int v,w,s;cin>>v>>w>>s;
        if(s!=-1&&s!=0&&s!=1){
            int cnt=0,k;
            for(k=1;k<=s;k<<=1){
                vv[++cnt]=k*v;ww[cnt]=k*w;
                s-=k;
            }
            if(s)vv[++cnt]=s*v;ww[cnt]=s*w;
            for(int k=1;k<=cnt;k++){
                for(int j=m;j-vv[k]>=0;j--){
                    dp[j]=max(dp[j],dp[j-vv[k]]+ww[k]);
                }
            }
        }
        else if(s==-1||s==1)
            for(int j=m;j>=v;j--)dp[j]=max(dp[j],dp[j-v]+w);
        else if(s==0)
            for(int j=v;j<=m;j++)dp[j]=max(dp[j],dp[j-v]+w);
    }
    cout<<dp[m];
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值