ACM CSP竞赛笔记(十九)——动态规划-背包问题

参考课程是我高中信息竞赛邱老师的课程。

【16-3-1 背包问题】 https://www.bilibili.com/video/BV1LZ421475D/?share_source=copy_web&vd_source=2c56c6a2645587b49d62e5b12b253dca

【16-3-2 01背包】 https://www.bilibili.com/video/BV1XH4y1M7iZ/?share_source=copy_web&vd_source=2c56c6a2645587b49d62e5b12b253dca

【16-3-3 完全背包】 https://www.bilibili.com/video/BV1Rs421A7y2/?share_source=copy_web&vd_source=2c56c6a2645587b49d62e5b12b253dca

【16-3-4 多重背包】 https://www.bilibili.com/video/BV1RD421A7Yh/?share_source=copy_web&vd_source=2c56c6a2645587b49d62e5b12b253dca

【16-3-5 混合背包】 https://www.bilibili.com/video/BV16y411Y7ua/?share_source=copy_web&vd_source=2c56c6a2645587b49d62e5b12b253dca

【16-3 背包习题解答】 https://www.bilibili.com/video/BV1UM4m1k7EM/?share_source=copy_web&vd_source=2c56c6a2645587b49d62e5b12b253dca

完整CSP ACM板子可以在我资料获取,我设置的0积分,如果要花钱来B站私我!

背包问题

01背包

如果有多个状态,用struct Node存储,不要用现成的数据结构。

空间优化——逆向滚动数组

从后向前覆盖

    for(int i=1;i<=r;i++){
        for(int j=i;j>=1;j--){
            int t;
            cin>>t;
            F[j]=max(F[j],F[j-1])+t;
        }
    }
for(int i=1;i<=M;i++){//对于每一棵草都判断(对每一件物品判断)
        for(int t=T;t>=0;t--){(如果装得下(倒着装))
            if(t>=herb[i].t){
                F[t]=max(F[t],F[t-herb[i].t]+herb[i].v);(取或不取)
            }
        }
    }

P1048 采药问题

https://www.luogu.com.cn/problem/P1048

#include<bits/stdc++.h>
using namespace std;

//时间、价值、没有数量限制,最多100
//时间是容量 价值是价值 如果如果100助都能采集完就输出100,如果不能就是01背包

struct Node{
    int t,v;
};
int T,M;
Node herb[105];
int F[1005];
int main(){
    cin>>T>>M;
    for(int i=1;i<=M;i++){
        cin>>herb[i].t>>herb[i].v;
    }
    F[0]=0;
    for(int i=1;i<=M;i++){//对于每一棵草都判断
        for(int t=T;t>=0;t--){
            if(t>=herb[i].t){
                F[t]=max(F[t],F[t-herb[i].t]+herb[i].v);
            }
        }
    }
    cout<<F[T];
}

常数优化

实际上如果背包很大,那么后面如果时间足够就不需要算了,比如还剩250秒,还剩20颗,这20颗的花费时间是200秒,那之间用F[40]计算输出即可。在循环中当时间t大于采集完后面所有草药和的时候,就直接全选跳出。

用一个逆序后缀和存储.

练习 P2925

https://www.luogu.com.cn/problem/P2925

#include<bits/stdc++.h>
using namespace std;
int C,H;
int hay[50005];
int F[50005];
//F[j]存容积为i时装的最大体积
//hay[i]存每个稻草的体积
int main(){
    cin>>C>>H;
    for(int i=1;i<=H;i++){
        cin>>hay[i];
    }
    for(int i=1;i<=H;i++){
        for(int j=C;j>=hay[i];j--){
            F[j]=max(F[j],F[j-hay[i]]+hay[i]);//不选则不变 选则容积减少hay[i]
        }
    }
    cout<<F[C];
}

完全背包

转移方程:

取这个类——取一件,取完还能取这个类! F[i][v-vi]+wi 下次还能取i

不取这个类——F[i-1][v]

空间优化——正向滚动数组

for(int i=1;i<=m;i++){
        for(int j=herb[i].t;j<=T;j++){
            F[j]=max(F[j],F[j-herb[i].t]+herb[i].v);   
        }
    }

练习 P1616

https://www.luogu.com.cn/problem/P1616

#include<bits/stdc++.h>
using namespace std;
struct Node{
    int t,v;
};
int presum[10005];
Node herb[10005];//草药的种类10005 herb存草药的时间和价值
int T,m;
long long F[100000005];//记录F[i]时间下最大价值
int main(){
    cin>>T>>m;
    for(int i=1;i<=m;i++){
        cin>>herb[i].t>>herb[i].v;
    }
    for(int i=1;i<=m;i++){
        for(int j=herb[i].t;j<=T;j++){
            F[j]=max(F[j],F[j-herb[i].t]+herb[i].v);   
        }
    }
    cout<<F[T];
}

练习 P2722

https://www.luogu.com.cn/problem/P2722

#include<bits/stdc++.h>
using namespace std;
struct Node{
    int t,s;
};
Node Q[10005];
int m,n;
int F[100005];
int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>Q[i].s>>Q[i].t;
    }
    for(int i=1;i<=n;i++){
        for(int j=Q[i].t;j<=m;j++){
            F[j]=max(F[j],F[j-Q[i].t]+Q[i].s);
        }
    }
    cout<<F[m];
}

多重背包

k就是放多少件,不放就是0,最多放p[i]。

对于当前物品,只有取k件然后进入下一个物品的选择。

练习 P1776

https://www.luogu.com.cn/problem/P1776

空间优化——逆向滚动数组

#include<bits/stdc++.h>
using namespace std;
int n,W;
struct Node{
    int v,w,m;
};
Node P[105];
int F[100005];//存当前空间下的最大价值
int main(){
    cin>>n>>W;
    for(int i=1;i<=n;i++){
        cin>>P[i].v>>P[i].w>>P[i].m;
    }
    for(int i=1;i<=n;i++){//对于每类宝物
        for(int j=W;j>=0;j--){
            for(int k=0;k<=P[i].m&&j>=k*P[i].w;k++){
                F[j]=max(F[j],F[j-k*P[i].w]+k*P[i].v);   
            }
        }
    }
    cout<<F[W];
}

时间优化——二进制拆分

要x件可以拆为一堆二进制堆,看看要不要这一堆,就变成了logn的01背包问题

注意定义,下面的模板 w是价值 v是空间。和我习惯相反。下下面是我的模板。

#include<bits/stdc++.h>
using namespace std;
int n,W;
int cnt=1;
int w[105],v[105],m[105];
int w1[100005],v1[100005];
int F[100005];//存当前空间下的最大价值
int main(){
    cin>>n>>W;
    for(int i=1;i<=n;i++){
        cin>>v[i]>>w[i]>>m[i];
        int temp=1;
        while(temp<=m[i]){//二进制拆分 当temp<=m[i] 即二进制数<个数时
            w1[cnt]=temp*w[i];//把temp个物品的重量合并,塞进w1中
            v1[cnt]=temp*v[i];//把temp个物品的价值合并,塞进v1中
            cnt++;
            m[i]-=temp;//从i类物品数量中减去拆走的部分
            temp<<=1;
        }
        w1[cnt]=m[i]*w[i];//剩余的重量放一堆
        v1[cnt]=m[i]*v[i];//剩余的价值放一堆
        cnt++;//下一类物品继续拆
        
    }
    //一定要注意,后面转换为01背包后 用的变量都是w1 v1修改后的内容了。
    for(int i=1;i<=cnt;i++){
        for(int j=W;j>=w1[i];j--){
            F[j]=max(F[j],F[j-w1[i]]+v1[i]);
        }
    }
    cout<<F[W];
}

混合背包

三种背包的初始状态 状态定义 最终目的都是一样的,唯一不同的是转移方程。

因此可以提前判断,然后根据不同的条件写for,从前往后从后往前滚动数组。

另一种想法是,混合背包就是多重背包,对于01背包本身就是多重的一种,完全背包也不是无限的,而是有界的,最多装:背包大小/物品大小。

处理链:【混合->多重->二进制拆分->01背包】

模板 P1833 樱花

处理链:【混合->多重->二进制拆分->01背包】

https://www.luogu.com.cn/problem/P1833

使用 cin 配合字符变量

用 cin,可以定义一个 char 变量专门用来“吃掉”冒号。

int h1, m1, h2, m2, n;
char c; // 用来接收冒号
cin >> h1 >> c >> m1 >> h2 >> c >> m2 >> n;
#include<bits/stdc++.h>
using namespace std;
int w[100005],v[100005],P[100005];
int w1[100005],v1[100005];
//n是数量 T是空间
int F[100005];
int cnt=1;
int main(){
    int h1,h2,m1,m2,n,T;
    char c;
    cin>>h1>>c>>m1>>h2>>c>>m2>>n;
    T=abs(h1*60+m1-h2*60-m2);
    for(int i=1;i<=n;i++){
        int p;
        cin>>w[i]>>v[i]>>p;
        if(p==0){
            P[i]=T/w[i];//最大容量/当前物品体积=最大数量
        }else{
            P[i]=p;
        }
        int temp=1;
        while(temp<=P[i]){
            w1[cnt]=temp*w[i];
            v1[cnt]=temp*v[i];
            cnt++;
            P[i]-=temp;
            temp<<=1;
        }
        w1[cnt]=P[i]*w[i];
        v1[cnt]=P[i]*v[i];
        cnt++;
    }
    for(int i=1;i<=cnt;i++){
        for(int j=T;j>=w1[i];j--){
            F[j]=max(F[j],F[j-w1[i]]+v1[i]);
        }
    }
    cout<<F[T];

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值