背包问题(二)

一、多重背包问题(单调队列优化)

在这里插入图片描述

这里有一个小问题,我还是有点疑问,当你选取的当前物品数量已经超过了当前物品的个数,但是体积还未超过背包容量,是否还会更新dp(下面是Al的回答)

现在,关于您提到的“选取的物品个数超过了当前物品的个数”的情况,实际上在代码中并不会发生。因为我们在更新dp[k]之前,会通过条件检查来确保不会违反物品的数量限制。具体来说,条件if (pp <= tt && k - s * v > q[pp])是用来检查队列头的状态是否对应了一个超过限制的容量(这里“超过限制的容量”实际上是指,如果从这个容量对应的物品数量开始继续考虑当前物品,那么将会违反物品的数量限制)。如果超过了,我们就移动队列头,直到找到一个不超过限制的容量或者队列为空。

我的思考是:当你选取的物品数量已经超过了当前物品的个数,但是体积还未超过背包容量,因为你是一个单调递减的队列,所以每次更新是基于对头更新的,队尾不会对对头造成影响。

#include<iostream>
#include<cstring> // 用于内存拷贝函数memcpy
using namespace std;
 
// 定义常量M,表示物品的最大数量乘以最大容量,用于数组大小
const int M = 20010;
 
// n表示物品种类数,m表示背包容量
int n,m;
// pre数组用于存储上一轮动态规划的状态,dp数组用于存储当前轮的状态
int pre[M],dp[M],q[M]; // q数组用于存储单调队列中的元素索引
 
int main()
{
    cin >> n >> m; // 输入物品种类数和背包容量
    for(int i = 0;i < n;i ++) // 遍历每种物品
    {
        memcpy(pre, dp, sizeof dp); // 将当前dp数组的状态复制到pre数组中,为下一轮动态规划做准备
        int v,w,s; // v表示物品体积,w表示物品价值,s表示该物品的最大数量
        cin >> v >> w >> s;
        for(int j = 0;j < v;j ++) // 由于物品数量s和体积v有关,这里通过遍历v的余数来优化状态转移
        {
            int pp = 0, tt = -1; // pp和tt分别表示单调队列的头和尾
            // 遍历所有可能的容量,k表示当前考虑的背包容量
            for(int k = j;k <= m;k += v)
            {
                // 如果队列头已经超出了s*v的限制(即队列头对应的物品数量超过了s),则移动队列头
                if(pp <= tt && k - s * v > q[pp])
                    pp ++;
                
                // 维护单调队列的性质,确保队列中的元素对应的dp值减去相应的成本(即(k - q[pp])/v * w)是递减的
                while(pp <= tt && pre[q[tt]] - (q[tt] - q[pp])/v * w <= pre[k] - (k - q[pp])/v * w)
                    tt --;
                
                // 如果队列不为空,则更新dp[k]为队列头元素对应的dp值加上当前物品的价值
                if(pp <= tt)
                    dp[k] = max(dp[k], pre[q[pp]] + (k - q[pp])/v * w);
                
                // 将当前容量k加入队列尾
                q[++ tt] = k;
            }
        }
    }
    cout << dp[m]; // 输出背包容量为m时的最大价值
    return 0;
}

二、庆功会

这个题就是上面的板子,每什么好说的

板子题链接
在这里插入图片描述

#include<iostream>
#include<cstring>
using namespace std;
const int M = 20010;
int n,m;
int pre[M],dp[M],q[M];

int main()
{
    cin >> n >> m;
    for(int i = 0;i < n;i ++)
    {
        int v,w,s;
        cin >> v >> w >> s;
        memcpy(pre, dp, sizeof dp);
        for(int j = 0;j < v;j ++)
        {
            int pp = 0, tt = -1;
            for(int k = j;k <= m;k += v)
            {
                if(pp <= tt && k - s * v > q[pp])
                pp ++;
                
                while(pp <= tt && pre[q[tt]] - (q[tt] - q[pp])/v * w <= pre[k] - (k - q[pp])/v * w)
                tt --;
                
                if(pp <= tt)
                dp[k] = max(dp[k], pre[q[pp]] + (k - q[pp])/v * w);
                
                q[++ tt] = k;
            }
        }
    }
    cout << dp[m];
    return 0;
}

三、混合背包

题目链接
在这里插入图片描述

这里把01背包当场特殊的完全背包

在这里插入图片描述

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1100;
int f[N];
int n,m;

int main()
{
    cin >> n >> m;
    for(int i = 1;i <= n;i ++)
    {
        int v,w,s;
        cin >> v >> w >> s;
        if(!s)
        {
            for(int j = v;j <= m;j ++)
            f[j] = max(f[j], f[j - v] + w);
        }
        else
        {
            if(s == -1) s = 1;
            
            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;
            }
            
            for(int j = m;j >= s * v;j --)
            f[j] = max(f[j], f[j - s * v] + s * w);
        }
        
    }
    
    cout << f[m];
    return 0;
}
### 完全背包问题中的进制优化原理 完全背包问题是动态规划领域的一个经典问题,其核心在于允许每种物品有无限数量可供选择。然而,在实际应用中,如果直接处理大量重复的物品,可能会导致计算复杂度显著增加。因此引入了 **进制优化** 技术来降低时间和空间开销。 #### 1. 进制优化的核心思想 进制优化基于这样一个事实:任何正整数都可以被唯一分解成若干个 $2^k$ 的形式(即通过进制表示)。对于给定的一种物品,假设它的最大可选数量为 $n_{\text{max}}$,可以通过将其拆分为多个子物品的方式实现高效求解。这些子物品的数量分别为 $1, 2, 4, \dots, 2^k$ 和剩余部分 $(n_{\text{max}} - (2^{k+1} - 1))$,其中 $2^k$ 是不超过 $n_{\text{max}}$ 的最大幂次[^4]。 这种划分方式使得我们可以用较少的操作次数覆盖所有可能的选择情况,从而减少状态转移过程中的冗余操作。 #### 2. 实现细节 以下是具体实现过程中涉及的关键点: - **物品分组** 将原始物品按照上述方法分成多组新物品,每一组对应于原物品的不同倍率组合。例如,若某物品最多可以选择7件,则它会被分割为三个独立的新物品,分别代表选取1件、2件和4件的情况。 - **更新状态方程** 对于每一个重新定义后的“虚拟”物品,仍然采用标准的0/1背包策略进行状态转移。设当前考虑的是第$i$类物品及其对应的重量$v_i$与价值$c_i$,则状态转移关系如下所示: ```python dp[j] = max(dp[j], dp[j-w[i]] + c[i]) ``` 这里$j$表示当前背包总容量,而`dp[]`数组存储着不同容量下的最优解值[^1]。 - **效率提升分析** 使用传统方法逐一尝试放置任意数量相同类型的物件时,时间复杂度通常达到O(nm),其中$n$指代物品种类总数,$m$则是目标容器大小;但经过此番改造之后,由于有效减少了候选集合规模,整体性能得以大幅改善至接近线性的水平[^2]。 #### 示例代码展示 下面给出一段Python伪代码片段用来演示如何运用该技巧完成一次完整的迭代运算: ```python def binary_optimized_complete_pack(items, capacity): n = len(items) # 初始化DPdp = [0]*(capacity+1) for item in items: value, weight, count = item # 利用进制拆分技术预处理单个商品 k = 1 while k <= count: v = k * value w = k * weight # 更新DP表如同常规0/1背包做法一样 for j from capacity downto w do: if j >=w : dp[j]=max(dp[j],dp[j-w]+v ) count -=k k *=2 # 如果还有残留的部分单独再算一遍 if count>0: v=count*value w=count*weight for j from capacity downto w do: if j>=w : dp[j]=max(dp[j],dp[j-w ]+v ) return dp[-1] ``` ### 结论 通过对完全背包问题实施进制优化措施,不仅能够简化逻辑结构而且还能极大地提高程序执行速度,尤其适用于那些存在较多同质化选项的实际场景之中[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

早睡早起^_^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值