算法学习笔记(8.8)-多重背包

目录

Question:

思路解析:

代码示例

多重背包的优化问题:

1.二进制优化

代码示例:

2.单调队列优化(滑动窗口)

代码示例

Question:

4. 多重背包问题 I - AcWing题库https://www.acwing.com/problem/content/description/4/

多重背包简单来说其实就是一个背包问题的变式,到底可以怎么去理解多重背包呢,说白了其实就是01背包和完全背包的结合,主要的优化方法就是二进制分组优化,单调队列优化方法

思路解析:

我们到底该怎么去理解转化呢,我们可以这样去思考问题,同一件物品可以重复去拿,10个相同的苹果体积为2价值为3,我们可以看成一个大苹果,体积为10*2,价值为10*3,此时就转化成了完全问题,选还是不选的问题,此时的选与不选是要在同一种物品的不同个体间做选择,直至我们的背包无法装下我们的这种物品。(此时的对于转化成完全的限制条件又是什么呢,我们只需要考虑我们的物品的 数量 * 体积 >= 背包的总体积,就可以转化成完全背包问题,我们可以在同种物品的不同个体间进行选择);对于 数量 * 体积 < 背包的体积,我们又该如何去考虑呢,我们可以这样子去思考,如果我们可以将相同的很多件物品看作一件,这不就转化成了0-1背包问题嘛,此时我们需要取枚举,我们实际需要拿多少个K,也就是所谓的当前背包的体积J > K * V[I],我们就可以全部的K装进背包。

(针对于代码间的问题说明,我们均采用了空间优化的方法,对于0-1背包而言我们需要考虑覆盖问题,但是完全背包不需要考虑)

代码示例

cpp 复制代码
// c++ 代码示例

#include <iostream>
#include <algorithm>

using namespace std ;

const int MAXN = 101 ;
int n, V ;
int v[MAXN], w[MAXN], s[MAXN] ;
int f[MAXN] ;

int main()
{
    // 获取基本的输入
    cin >> n >> V ;
    for (int i = 1 i <= n ; i++)
    {
        cin >> v[i] >> w[i] >> s[i] ;
    }
    // 以下代码均是空间优化后的
    // 0-1背包和完全背包的转化过程思想,遍历每一种物品    
    for (int i = 1 ; i <= n ; i++)
    {     
        if (s[i] * v[i] >= V)
        {
            for (int j = v[i] ; j <= V ; j++)
            {
                f[j] = max(f[j], f[j - v[i]] + w[i]) ;
            }
        }
        // 0-1背包
        else
        {
            for (int j = V ; j >= v[i] ; j--)    
            {
                for (int k = s[i] ; k >= 0 ; k--)
                {
                    if (j >= k * v[i])
                    {
                        f[j] = max(f[j], f[j - k * v[i]] + k * w[i]) ;
                    }
                }
            }
        }
    }
    cout << f[V] ;
    return 0 ;
}
python 复制代码
# python 代码示例

n,V = map(int,input().split())

v = [0] * (101 + 1)
w = [0] * (101 + 1)
s = [0] * (101 + 1)
f = [0] * (101 + 1)

for i in range(1, n + 1) :
    v[i], w[i], s[i] = map(int, input().split())

for i in range(1, n + 1) :
    if (s[i] * v[i] >= V) :
        for j in range(v[i], V + 1) :
            f[j] = max(f[j], f[j - v[i]] + w[i])
    else :
        for j in range(V, v[i] - 1, -1) :
            for k in range(s[i], -1, -1) :
                if (j >= k * v[i]) :
                    f[j] = max(f[j], f[j - k * v[i]] + k * w[i])

print(f[V])

基本思想:比如第i件物品有s个,我可以把相同种类 的物品的进行合并,比如我拿出两件合并出一个新的物品,我拿出三件合并出一个新的物品,以此类推,我拿出s个合并出一个新的物品。基于这种思想,我们把第i件的s个物品转换为s种体积各不相同的物品,然后在用01背包的思想,求出最优解!

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std ;

int N, V ;
int dp[1010] ;

int main()
{
    cin >> N >> V ;
    int v, w, s ;
    for (int i = 1 ; i <= N ; i++)
    {
        cin >> v >> w >> s ;
        for (int j = V ; j > 0 ; j--)
        {
            for (int k = 1 ; k <= s && k * v <= j ; k++)
            {
                dp[j] = max(dp[j], dp[j - k * v] + k * w) ;
            }
        }
    }
    cout << dp[V] ;
    return 0 ;
}

多重背包的优化问题:

1.二进制优化

5. 多重背包问题 II - AcWing题库https://www.acwing.com/problem/content/5/

原理:

一个数字,我们可以按照二进制来分解为1 + 2 + 4 + 8 ...... +2^n + 余数

十进制数字7,可以从二进制100,010,001做加和得到即111,001为1,010为2,100为4,也就是1、2、4,用1、2、4可以表示1~7中任意一个数。

再比如,10,可以分为1,2,4,3这个三是怎么来的呢? 3就是余数!

通过上述原理,我们可以把第i件物品的s件,按二进制思想分为1,2,4...到剩余。这样从复杂度为s,降到了(log2S)。时间复杂度为O(V*Σlog n[i])。

代码示例:
cpp 复制代码
// c++代码示例
#include <iostream>
#include <algorithm>

using namespace std ;

const int MAXN = 1e5 + 10 ;

int n , V ;
int v[MAXN], w[MAXN] ;
int f[MAXN] ;

int main()
{
    cin >> n >> V ;
    int cnt = 0 ;
    for (int i = 1 , a, b, s ; i <= n; i++)    
    {
        cin >> a >> b >> s ;
        int k = 1 ;
        while (k <= s)
        {
            v[++cnt] = k * a ;
            w[cnt] = k * b ;
            s -= k ;
            k *= 2 ;
        }
        if (s)
        {
            v[++cnt] = s * a ;
            w[cnt] = s * b ;
        }
    }
    for (int i = 1 ; i <= cnt ; i++)
    {
        for (int j = V ; j >= v[i] ; j--)
        {
            f[j] = max(f[j], f[j - v[i]] + w[i]) ;
        }
    }
    cout << f[V] ;
    return 0 ;
}
cpp 复制代码
#include <iostream>
#include <algorithm>

using namespace std ;
const int maxn = 6e3 + 10 ;

int n , m , ans ;

int f[maxn] ;

int main()
{
    cin >> n >> m ;
    
    for (int i = 1 ; i <= n ; i++)
    {
        int v, w, s ;
        cin >> v >> w >> s ;
        for (int k = 1 ; k <= s ; k *= 2)
        {
            for (int j = m ; j >= k * v ; j--)
            {
                f[j] = max(f[j], f[j - k * v] + k * w) ;
            }
            s -= k ;
        }
        if (s)
        {
            for (int j = m ; j >= s*v ; j--)
            {
                f[j] = max(f[j], f[j - s * v] + s * w) ;
            }
        }
    }
    cout << f[m] ;
    return 0 ; 
}

2.单调队列优化(滑动窗口)

6. 多重背包问题 III - AcWing题库https://www.acwing.com/problem/content/6/

利用单调队列优化动态规划的状态转移过程。它通过维护一个单调递减的队列,确保在每次状态转移时可以高效地找到最优的前驱状态,从而减少时间复杂度。

我们可以这样理解这个问题,就是每一次拿到该种类的物品的时候,就把这种物品的思考为0-v-1的单调队列,然后利用双指针维护最优解,需要考虑窗口维护的时候,我们要知道单调队列里里面存储到底是什么其实是j , j + v , j + 2 * v......等,到最后进行维护的便是这个单调队列,最优解是{ f [j], f [j+v], f [j+2*v], f [j+3*v], ... , f [j+k*v] } 中的最大值,稍微转化一下f [j+k*v] = max( f [j], f[j+v] - w,..., f [j+k*v] - k*w) + k*w,我们就可以通过一个中介数组进行动态更新了。q数组 中保存了上述写的 当 f [j+k*v] - k*w 最大时的下标, 入队前如果队首不在滑动窗口内,队首出队 ,每次入队元素如果比队列中元素大,就弹出队尾

代码示例
cpp 复制代码
// c++ 代码示例

#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std ;

const int MAXN = 20010 ;
int n ,f[MAXN], q[MAXN], pre[MAXN];

int main()
{
    int n, V ;
    cin >> n >> V ;
    for (int i = 1, v ,w ,s ; i <= n ; i++)    
    {
        cin >> v >> w >> s ;
        memcpy(pre, f, sizeof(f)) ;
        for (int j = 0 ; j < v ; j++)
        {
            int hh = 0 , tt = -1 ;
            for (int t = j ; t <=V ; t += v)
            {
                if (hh <= tt && t - s * v > q[hh])
                {
                    hh++ ;
                }
                while (hh <= tt && pre[q[tt]] - (q[tt] - j) / v * w <= pre[t] - (t - j) / v * w)
                {
                    tt-- ;
                }
                if (hh <= tt)
                {
                    f[t] = max(f[t], pre[q[hh]] + (t - q[hh]) / v * w) ;
                }
                q[++tt] = t ;
            }
        }
    } 
    cout << f[V] ;
    return 0 ;
}
相关推荐
大翻哥哥1 小时前
Python 2025:量化金融与智能交易的新纪元
开发语言·python·金融
~|Bernard|1 小时前
在 PyCharm 里怎么“点鼠标”完成指令同样的运行操作
算法·conda
战术摸鱼大师1 小时前
电机控制(四)-级联PID控制器与参数整定(MATLAB&Simulink)
算法·matlab·运动控制·电机控制
Christo31 小时前
TFS-2018《On the convergence of the sparse possibilistic c-means algorithm》
人工智能·算法·机器学习·数据挖掘
汇能感知2 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun2 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
好家伙VCC2 小时前
数学建模模型 全网最全 数学建模常见算法汇总 含代码分析讲解
大数据·嵌入式硬件·算法·数学建模
zhousenshan2 小时前
Python爬虫常用框架
开发语言·爬虫·python
茯苓gao2 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾3 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang