01背包/完全背包/多重背包-优化/分组背包整理

这篇博客详细介绍了动态规划在解决背包问题中的应用,包括01背包、完全背包和多重背包的优化解法。01背包采用逆序遍历优化,完全背包通过正序遍历简化状态转移,多重背包利用二进制优化处理物品无限个的情况。此外,还提及了单调队列优化和分组背包问题,展示了如何在不同场景下灵活运用动态规划策略。
摘要由CSDN通过智能技术生成

01背包

f[i,j]表示前i件物品,体积不超过j的最大价值
每件物品一个
转移方程
在这里插入图片描述
由于i状态转移只用到f[i-1],故优化掉第一维,具体来说计算f[j]的时候要有上层f[j-v],由于去掉第一维度后本身就是上一层的值,所以需要逆序遍历,保证j-v是上层的j-v.

/*
 * @Author: ACCXavier
 * @Date: 2021-04-25 19:57:50
 * @LastEditTime: 2021-05-24 11:54:01
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:https://www.acwing.com/problem/content/2/
 * @keywords: 01背包
*/
#include <iostream>
using namespace std;
const int N = 2020;
int f[N];//状态
int main()
{
    int n,m;
    cin>>n>>m;int v,w;
    for(int i = 0;i < n; ++ i){
        cin>>v>>w;
        for(int j = m; j >= v; --j)f[j] = max(f[j],f[j-v]+w);
    }
    cout<<f[m]<<endl;

    return 0;
}

完全背包

f[i,j]表示前i件物品,体积不超过j的最大价值
每件物品可拿无限
转移方程
在这里插入图片描述
注意这里物品无限,所以拿到体积j拿不下为止,所以f[i,j]和f[i,j-v]的末项都是一样的,所以可以合并
由于i状态转移只用到f[i]层,故优化掉第一维,具体来说计算f[j]的时候要有本层f[j-v], (注意状态方程里面是f[i,j-v]+w) 所以要保证计算i,j前i,j-v要被算过,正序遍历.

/*
 * @Author: ACCXavier
 * @Date: 2021-04-26 14:48:50
 * @LastEditTime: 2021-04-26 15:09:45
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:https://www.acwing.com/problem/content/3/
 * @keywords: 完全背包问题
 */
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int main() {
    int n,m,v,w;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin>>v>>w;
        for (int j = v; j <= m; j++) {
            f[j] = max(f[j],f[j-v]+w); 
        }
    }
    cout<<f[m]<<endl;

    return 0;
}

多重背包

f[i,j]表示前i件物品,体积不超过j的最大价值
每件物品可拿s[i]个
转移方程

其中k表示s[i]中拿k个
这里是不能像完全背包那样优化,因为
在这里插入图片描述f[i,j-v]表示前i件物品,体积不超过j-v的拿法,拿第i个最多拿s[i]个,故每一个f[i.j-v]都有s[i]项,对齐后后面会多一项,不能由f[i,j-v]和f[i-1,j-(s+1)v]+sw)直接求出f[i,j]最大值.

  • 比如f[i,j-v]是140,f[i-1,j-(s+1)v]+sw)也是140,就求不出f[i-1,j-v] ,f[i-1,j-2v]+ w,…,f[i-1,j-s[i]v]+(s[i]-1)w的最大值,也就求不出f[i,j]的值

二进制优化

假设A物品由888个,可以拆成1,2,4,8,16,32,64,128,256,377,可以保证这些数字可以凑出1~888之间任何数字,故只需要把888个A物品打包为这些个数A物品的包,看成01背包按包拿即可.由于物品n个被拆成logn个,时间复杂度为O(nmlogn),m是物品大小

/*
 * @Author: ACCXavier
 * @Date: 2021-04-27 16:16:10
 * @LastEditTime: 2021-05-24 12:22:10
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:https://www.acwing.com/problem/content/5/
 * @keywords: 多重背包II 二进制优化
 */
#include <iostream>
using namespace std;
const int M = 2010;
int f[M];

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        int v, w, s;
        cin >> v >> w >> s;//每种物品s个
        for (int k = 1; k <= s; k *= 2) {//拆分包的大小为1,2,4,...
        //拆成二进制次幂之后比如 k = 4,4个物品一打包
            //01背包更新单个物品状态方程
            for (int j = m; j >= k * v; --j) {//j>=单个物品体积(其实是4个打包)
                f[j] = max(f[j], f[j - k * v] + k * w);//01背包
            }
            s -= k;//物品数量更新,888-1-2-4-...
        }
        if (s)//多出来一部分,上面就是888中的377
            for (int j = m; j >= s * v; j--)
                f[j] = max(f[j], f[j - s * v] + s * w);
    }
    cout << f[m] << endl;

    return 0;
}

单调队列优化

什么时候我把代码看懂了再更新
思路是
在这里插入图片描述

分组背包

f[i,j]表示前i组物品,体积不超过j的最大价值
每组物品可拿某一个,每组s[i]种物品
和01背包相比多一层组内遍历,看选不选改组和组内选哪一个

/*
 * @Author: ACCXavier
 * @Date: 2021-04-28 22:48:06
 * @LastEditTime: 2021-05-23 21:24:43
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:https://www.acwing.com/problem/content/description/9/
 * @keywords: 分组背包
 */
#include <algorithm>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 110;
int f[N], v[N][N], w[N][N], s[N];
int n, m;
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> s[i];
        for (int j = 1; j <= s[i]; ++j) {//@1从1开始,第i组物品的第j种
            cin >> v[i][j] >> w[i][j];
        }
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = m; j >= 0; --j) {
            for (int k = 0; k <= s[i]; ++k) {//能够遍历到=s[i]是因为@1处下标从1开始,第s[i]种的下标就是s[i],如果@1处从0开始则这里k不能取到s[i]//另外,不拿第i组等价于f[j] = f[j],所以这里k取1和0都可以 (但是二维就必须k从0开始)
                if (v[i][k] <= j) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);//@v[i][k] 第i组的第k种 不是v[i][j]
            }
        }
    }
    cout << f[m];

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值