动态规划—— 背包问题 解题记录

一些想法:

        现在是2024-3-17 09:27:36,好摸啊这两天。

解题报告:

        背包问题模型是在有限的背包内,寻找一个贡献最大值的取物品的方案。可以利用二维状态 f[i][j] 来表示 前 i 个物品中,背包容量最大是 j 的,贡献最大的方案。背包方案的转移一般通过最后一个物品来分类。

        AcWing 2. 01背包问题

        最基础的背包问题。转移方程:

    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i-1][j-v]+w);
        }

        滚动数组优化,观察转移方程,由于背包转移所需要的是在上一层,所以要反向遍历,防止使用已被更新的数据:

    for(int i=1;i<=n;i++){
        for(int j=m;j>=v;j--)
            f[j]=max(f[j],f[j-v]+w);
    }

        AcWing 3. 完全背包问题

        完全背包问题,通过一层公式来进行转换

    f[i][j]= max{f[i-1][j],f[i-1][j-1*v]+1*w, f[i-1][j-2*v]+2*w......f[i-1][j-k*v]+k*w};
    f[i][j-v]= max{        f[i-1][j-1*v],     f[i-1][j-2*v]+1*w......f[i-1][j-k*v]+(k-1)*w};

        遂得转换方程如下

    for(int i = 1; i<=n ;i++)
    {
        int v,w;cin>>v>>w;
        for(int j  = 0 ;j <= m; j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=v)f[i][j]=max(f[i][j-v]+w,f[i][j]);
        }
    }

        滚动数组优化,观察原转移方程,由于方案转移所需要的数据在转移时是同一行,所以要从前往后遍历保证所需数据未被更新掉:

    for(int i = 1; i<=n ;i++)
    {
        int v,w;cin>>v>>w;
        for(int j  = v ;j <= m; j++)
        {
            f[j]=max(f[j-v]+w,f[j]);
        }
    }

        AcWing 4. 多重背包问题

        多重背包问题具有多种写法,首先最为朴素的三重循环,使用上一题完全背包中的朴素无公式转换的思路,直接循环拿取的物品数和应用限制物品数量的条件得到结果。

    //s表示物品的数量,
    for(int i = 1;i<=n ;i++)
    {
        cin>>v>>w>>s;
        for(int j = 0;j<=m;j++)
            for(int k = 0;k*v<=j&&k<=s;k++)
                f[i][j]=max(f[i][j],f[i-1][j-k*v]+k*w);
    }

        优化:

        把所有物品通过 s 来进行预处理,通过把 s 拆成二进制的每一位,遂可以直接用01背包完成题目,问题在于其空间需要扩大到 max{ V , log(S)*N}

int cnt = 0;
for(int i = 1;i <=n ;i++)
{
    int a,b,s;cin>>a>>b>>s;
    int k = 1;
   
    for(int k = 1; k <= s; k<<=1)
        cnt++,v[cnt]=k*a,w[cnt]=k*b,s-=k;
    
    if(s>0)
        cnt++,v[cnt]=s*a,w[cnt]=s*b;
}

         单调队列优化(今天拉屎的时候终于悟了的解法)

        当我们继续 尝试使用完全背包中的做法,我们会发现,他的最大值似乎不能通过一次减掉v来得到。而且其得到的最大值并不符合定义其最大值如下图

        显然右图中的最大值并非定义最大值。

        但是我们可以不断重复这个过程,直到所有矩形的全集被覆盖。那么在全集被渐渐覆盖的过程中,让这个体积只剩下余数,那么余数反过来慢慢往最开始的体积来加的话,在考虑的集合元素达到某一个数量 (题目中的物品数量s) 的时候,就可以开始得到其中的最大值,同时在不断更新集合中数据的过程中,把已经不符合条件的 (体积最小的元素) 退出,这就符合一个单调队列模型,就可以简单的对每一个数据是 m/v 的复杂度来处理 (v大小可省略为1),所以总的时间复杂度就是O(n*m) 的。

    for(int i = 1; i<= n; i ++)
    {
        cin>>v>>w>>s;
        for(int j = 0; j<v; j++)//遍历所有的余数,
        {
            int hh = 0 ,tt = -1;
            for(int k = j; k <=m;k+=v )
            {
                if(hh<=tt && q[hh] < k-s*v) hh++;
                
                //上次队尾的值没有补上现在的体积能放满的物品 所以补上后拿去和现在的值比较
                while(hh<=tt && f[i-1][q[tt]]+(k-q[tt])/v*w <= f[i-1][k]) tt--; 
                
                q[++tt] = k;
                f[i][k] = f[i - 1][q[hh]] + (k - q[hh]) / v * w;
            }
            
        }
    }

 

 AcWing 9. 分组背包问题

        同样的,分组背包只需要考虑不选和选哪个的问题,通过将01背包扩展,循环选哪个最优,三重循环结束战斗。

    for(int i=0;i<n;i++)
        for(int j=m;j>=0;j--)
            for(int k=0;k<s[i];k++)
                if(j>=v[i][k]) 
                  f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
心得:

        背包问题先写到这里,后面还有题目另开一篇博客。课好多感觉上不完,找不到时间来学算法竞赛了,烦的。

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值