背包九讲学习笔记 第一讲 01背包

第一讲 01背包


题目

给定物品个数n,背包容量v,每个物品都有一个体积c和价值w,要求向背包中装物品使得总价值最高.

基本思路

状态表示:f(i,j)表示前i个物品试图装入一个容量为j的背包的最大价值.
边界情况:f(0,j)=0.
状态转移:f(i,j)=max(f(i-1,j),f(i,j-save[i])+value[i]). 即装或不装第i个物品
时间复杂度O(VN) 空间复杂度O(VN)

例1 hdu2602 01背包裸题
两个wa点,第一个是多组数据没有注意到,第二个是存在体积为0的情况,所以j需要从0枚举起.

        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++) scanf("%d",&value[i]);
        for(int i=1;i<=n;i++) scanf("%d",&volume[i]);
        for(int i=1;i<=n;i++)
        for(int j=0;j<=v;j++)
            if(j<volume[i])
                    dp[i][j]=dp[i-1][j];
                else
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-volume[i]]+value[i]);
        printf("%d\n",dp[n][v] );

优化空间复杂度

注意到每个状态f(i,j)只与之前的状态f(i-1,j)和f(i-1,j-save[i])有关.实际上开一个二维数组是不必要的,完全可以开一个一维数组下标为j,然后循环递推n次.
即f(j)=max(f(j),f(j-save[i])+value[i]),但是正向循环是错误的,因为之前的j-save[i]已经被新状态所覆盖,正确的做法只要反转一下顺序,逆向循环就好.

        int n=read(),v=read();
        for(int i=1;i<=n;i++)value[i]=read();
        for(int i=1;i<=n;i++)volume[i]=read();
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
            for(int j=v;j>=volume[i];j--)
                dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);
        printf("%d\n",dp[v] );

注意此时这个数组的意义是:容量为j的背包最多能装多少价值的物品.
遍历i个物品后,得到结果.

初始化的细节问题(满包)

01背包满包问题:给定物品个数n,背包容量v,每个物品都有一个体积c和价值w,请问恰好装满背包时最大总价值是多少.
状态表示,转移方程同理:f(j)=max(f(j),f(j-save[i])+value[i]))
但是初始条件:f(j)=-∞,f(0)=0.意义是0件物品时不可能装到除0之外的容量,-∞表示无解.
递推结束后会有很多状态都是无解,表示不能达到.

例2 luogu P1164 小A点菜 01背包满包求方案数
状态表示:f(j)表示容量为j的背包装满有几种方案.
初始条件:f(j)=0,f(0)=1
状态转移:f(j)=f(j)+f(j-save[i])

    int n=read(),m=read();
    for(int i=1;i<=n;i++)
        save[i]=read();
    dp[0]=1;
    for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--)
            dp[j]=dp[j]+dp[j-save[i]];
    printf("%d\n",dp[m] );

一个常数优化

01背包二重循环的下限可以从Ci优化成max(V − Σ(i到n)C, Ci)

在这之前我有一个疑惑,为什么下限会是ci,这在未优化空间复杂度时是不成立的.
但是优化空间复杂度后显然成立,因为”什么都不做”就是dp[j]=dp[j].
所以转移方程就是

for(int i=1;i<=n;i++)
    for(int j=v;j>=volume[i];j--)
        dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);

下面考虑原文中的优化,这个数值是仅与i有关的.
看了网上大佬的思路,意思是某些算出来的背包实际上对最终结果是没有帮助的.
最多有帮助的就是总体积减去之后每个物品的体积之和.
比如计算最后一件物品时,V-C(n-1)以下容量的背包是不可能转移到结果的.
同理,计算倒数第二件物品时,V-C(n-1)-C(n-2)以下的背包也是不可能转移到最终结果的.

        int n=read(),v=read(),lim=v;
        for(int i=1;i<=n;++i) value[i]=read();
        for(int i=1;i<=n;++i) volume[i]=read(),lim-=volume[i];
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;++i)
        {
            int th=max(lim,volume[i]);
            for(int j=v;j>=th;--j)
                dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);
            lim+=volume[i];
        }
        printf("%d\n",dp[v] );

练习题

见之后的训练题博客.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值