首页 > 其他分享 >浅谈背包

浅谈背包

时间:2023-08-11 19:55:43浏览次数:43  
标签:背包 浅谈 int 复杂度 01 物品 include

引入

背包问题,是大多数 oier 在学习动态规划(下文用 dp 代替)的过程中,最先接触到的问题。它看似简单,有蕴含着无穷的变化。本文便对作者接触过的背包问题做一个总结。

背包问题,一般情况下指:你有 \(n\) 个物品和一个容量为 \(m\) 的背包,每个物品有重量 \(w\) 和价值 \(v\),各个物品间可能存在一些限制。请选择合适的方案使得在背包承载范围内获得最大的价值。

01背包

01背包,是最早接触到的背包问题。它是指每个物品只有 1 个。

暴力做法是直接搜索当前背包选还是不选,时间复杂度 \(\mathcal O(2^n)\)。

考虑 dp。设 \(f_{i,j}\) 表示到了第 \(i\) 个物品,当前背包总重量为 \(j\) 的方案数。那么有

\[f_{i,j}=\begin{cases}f_{i-1,j}&j<w_i\\\max(f_{i-1,j},f_{i-1,j-w_i}+v_i)&j\geqslant w_i\end{cases} \]

时间复杂度 \(\mathcal O(nm)\),空间复杂度 \(\mathcal O(nm)\)。

考虑对空间进行优化。注意到 \(f_i\) 都是由 \(f_{i-1}\) 转移得到,因此可以将这一维去掉,改为 \(f_j=\max(f_j,f_{j-w_i}+v_i)\)。

但注意,此时的 \(j\),应该从 \(m\) 倒叙循环至 \(w_i\)。因为 \(f_j\) 可能会从 \(f_{j-w_i}\) 转移过来,所以要保证在 \(f_j\) 未被更新前,\(f_{j-w_i}\) 仍然是循环至 \(i-1\) 时的 \(f_{j-w_i}\)。这是因为每个物品只能被选一次导致的。如果正序循环,就可能出现 \(f_{j-k\times w_i}\rightarrow f_{j-(k-1)\times w_i}\rightarrow \cdots \rightarrow f_{j-w_i}\rightarrow f_j\) 的情况,这显然是不合法的。

至此,空间复杂度便被优化至 \(\mathcal O(m)\)。

最终答案是 \(f_m\)。

例题:P1048 [NOIP2005 普及组] 采药

#include<cstdio>
#include<algorithm>
#define M 1005
using namespace std;
int n,m,f[M];
int main()
{
    scanf("%d%d",&m,&n);
    for (int i=1;i<=n;++i)
    {
        int w,v;
        scanf("%d%d",&w,&v);
        for (int j=m;j>=w;--j)//倒序循环
            f[j]=max(f[j],f[j-w]+v);
    }
    printf("%d\n",f[m]);
    return 0;
}

完全背包

完全背包与01背包类似,只是此时物品可以选无限个。而完全背包的代码和01背包的也很类似,只是在枚举 \(j\) 的时候由倒叙改为正序,因为此时上文所说的 \(f_{j-k\times w_i}\rightarrow f_{j-(k-1)\times w_i}\rightarrow \cdots \rightarrow f_{j-w_i}\rightarrow f_j\) 这种不合法的情况变成了合法。因此采用正序循环。时空复杂度同样分别为 \(\mathcal O(nm)\) 和 \(\mathcal O(m)\)。

例题:P1616 疯狂的采药

#include<cstdio>
#include<algorithm>
#define M 10000005
#define ll long long
using namespace std;
int n,m;
ll f[M];
int main()
{
    scanf("%d%d",&m,&n);
    for (int i=1;i<=n;++i)
    {
        int w,v;
        scanf("%d%d",&w,&v);
        for (int j=w;j<=m;++j)//正序循环
            f[j]=max(f[j],f[j-w]+(ll)v);
    }
    printf("%lld\n",f[m]);
    return 0;
}

多重背包

多重背包位于01背包和完全背包间,此时物品会新增一个 \(t_i\) 表示该物品最多能选多少次。

朴素做法是暴力枚举当前物品选多少个,然后同样是倒序循环。

for (int i=1;i<=n;++i)
    {
        int w,v,t;
        scanf("%d%d%d",&w,&v,&t);
        for (int j=m;j>=w;--j)
            for (int k=1;k<=t;++l)
                f[j]=max(f[j],f[j-k*w]+k*v);
    }

但这个做法不优秀,时间复杂度 \(\mathcal O(nmt)\)。而优化有两种,分别为二进制优化和单调队列优化。

二进制优化

我们注意到01背包的复杂度是较为优秀的,考虑将多重背包转换成01背包。

自然的想法是选 \(t\) 个变为 \(t\) 个同样的物品。可复杂度没有改变。

换种思路。我们知道任何一个正整数 \(x\),都可以表示成 \(2\) 的若干次方相加。此时最多有 \(\log_2 x\) 项。根据这个方向,我们考虑将 \(t\) 变为 \(2^0+2^1+\cdots +2^k+x\),这仍然不会超过 \(\log_2 t\) 项。而此时,我们可以任意选择这些项中的某些项,从而得到 \(0\sim t\) 中的任意一个数字。那么我们将原本最多选 \(t\) 次的物品改为这些最多选一次的物品,便完成了对最多选 \(t\) 次的拆分,将多重背包变为01背包。

例如,一个价值为 \(v\),重量为 \(w\),最多选 \(12\) 从的物品,可以拆分以下 \(4\) 个物品:

  1. 价值为 \(v\),重量为 \(w\) 的物品。
  2. 价值为 \(2v\),重量为 \(2w\) 的物品。
  3. 价值为 \(4v\),重量为 \(4w\) 的物品。
  4. 价值为 \(5v\),重量为 \(5w\) 的物品。

然后进行01背包的操作即可。时间复杂度 \(\mathcal O(nm\log t)\)。

例题:P1776 宝物筛选

#include<cstdio>
#include<algorithm>
#define N 2005
#define M 40005
using namespace std;
int n,m,num,ans,f[M];
struct node {int w,v;}a[N];
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
    {
        int v,w,t;
        scanf("%d%d%d",&v,&w,&t);
        for (int j=0;j<17;++j)
            if (t>=(1<<j))
            {
                a[++num].v=v*(1<<j);
                a[num].w=w*(1<<j);
                t-=(1<<j);
            }
        if (t) a[++num].v=v*t,a[num].w=w*t;
    }
    for (int i=1;i<=num;++i)
        for (int j=m;j>=a[i].w;--j)
            f[j]=max(f[j],f[j-a[i].w]+a[i].v);
    printf("%d\n",f[m]);
    return 0;
}

单调队列优化

我们可以进一步优化。对于每个物品,它每选一次只会更改 \(w\) 的重量。那不妨枚举余数 \(d\),然后枚举 \(f_{d+j\times w}\),用 \(f_{d+k\times w}+(j-k)\times v(j-k<t)\)。用单调队列维护 \(f_{d+k\times w}-k\times v\) 即可。

#include<cstdio>
#include<algorithm>
#define M 40005
using namespace std;
int n,m,res,ans,hd,tl,q[M],q2[M],f[M];
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
    {
        int v,w,t;
        scanf("%d%d%d",&v,&w,&t);
        if (w==0) {res+=v*t;continue;}//没有重量直接选
        t=min(t,m/w);
        for (int d=0;d<w;++d)
        {
            hd=1;tl=0;
            for (int j=0;j<=(m-d)/w;++j)
            {
                while (hd<=tl&&f[d+j*w]-j*v>=q2[tl]) --tl;
                q[++tl]=j;q2[tl]=f[d+j*w]-j*v;
                while (hd<=tl&&q[hd]<j-t) ++hd;
                f[d+j*w]=max(f[d+j*w],q2[hd]+j*v);
            }
        }
    }
    for (int i=1;i<=m;++i)
        ans=max(ans,f[i]);
    printf("%d\n",ans+res);
    return 0;
}

标签:背包,浅谈,int,复杂度,01,物品,include
From: https://www.cnblogs.com/Livingston/p/17623842.html

相关文章

  • 隐私计算之浅谈联邦学习
    本文分享自天翼云开发者社区《隐私计算之浅谈联邦学习》 作者:l****n一、背景“数据孤岛”简单的讲,各组织都持有各自的数据,这些数据之间互有关系但又独立存储于各组织。出于安全性、合规性等方面考虑,各组织只能查询、使用己方数据,无法交换其它组织的数据。在联邦学习出现前,针对......
  • 浅谈根号分治
    浅谈根号分治一、问题引入  给定一个长度为\(n\)的序列,进行\(m\)次询问。每次询问给出两个数字\(x,y\)。对于每次询问,输出所有下标模除\(x\)等于\(y\)的元素的总和。  对于这个问题,我们发现他要维护的是一段离散的元素的和,而我们平时学的数据结构,如线段树等都只能维护一段......
  • 浅谈弧光保护在中低压开关柜中的应用
    未晓妃安科瑞电气股份有限公司上海嘉定201801摘要:近年来,中低压开关柜的使用越来越普遍。在此过程中,确保开关柜平稳运行至关重要。并在此基础上,阐述了由此产生的弧光和电弧事故的危险性,引入弧光保护及母线保护的特点必要性分析,阐述了弧光保护系统的原理和结构。工程实例证明,弧光保......
  • CGAL入门——浅谈CGAL
    CGAL官网https://doc.cgal.org/latest/Manual/index.html最近在学习CGAL,发现CGAL中文资料太少了,官网示例代码也很少注释,还加入了很多自定义的很少见过的名词,易读性略差,学习起来有点难度赶紧记录一下学习过程,怕以后忘了 1.简介CGAL(ComputationalGeometryAlgorithmsLibrar......
  • 浅谈项目架构设计
    整理自b站up主主要一点是最合适的是最好的,不必为了过于追求某项技术而冗余!一.功能性需求1.跟实际的业务需求是对应的!2.所使用的技术框架是不是够先进,文档是否完善,使用过程中容易排查到问题3.技术是否为开源的,够不够活跃,更新频率等4.成本:学习成本,使用成本,迁移成本,维护成本,要......
  • 浅谈AI浪潮下的视频大数据发展趋势与应用
    视频大数据的发展趋势是多样化和个性化的。随着科技的不断进步,人们对于视频内容的需求也在不断变化。从传统的电视节目到现在的短视频、直播、VR等多种形式,视频内容已经不再是单一的娱乐方式,更是涉及到教育、医疗、商业等各个领域。为了满足用户个性化的需求,视频大数据的分析和挖掘......
  • 浅谈 2-SAT
    SAT是适定性(Satisfiability)问题的简称。一般形式为k-适定性问题,简称k-SAT。而当\(k>2\)时该问题为NP完全的。所以我们只研究\(k=2\)的情况。而2-SAT问题一般指的是,有\(n\)个布尔变量\(x_1,x_2\dotsx_n\),现在有若干个二元的运算,是对于\(x_i,\negx_i,x_j\neg......
  • 背包问题的一些模板
    01背包问题:无优化for(inti=1;i<=n;i++){for(intc=0;c<=m;c++){f[i][c]=f[i-1][c];if(c>=w[i])f[i][c]=max(f[i][c],f[i-1][c-w[i]]+v[i]);}}一维数组优化:for(inti=1;i<=n;i++){for(intc=m;c>=0;c--){......
  • 浅谈PLC程序命名3大通用规则
    导读工程师在编写PLC程序时,可能需要对项目中的程序块、变量表、单一背景数据块、全局DB块等命名。在博途软件中支持中文和英文的命名。但是一旦程序量比较大,命名可能就会出现混乱的现象。针对命名,只要读者遵循相关命名规则就不易发生混乱。本文以博途软件为例进行探讨。01......
  • 浅谈伯努利数
    O.前言在翻洛谷日报的时候居然没看到伯努利数的讲解,于是有了这篇文章。想要看懂本文,你需要提前知道以下内容:二项式系数;幂级数;艾弗森括号;下降幂;第二类斯特林数。部分内容在文中给了对应的公式,故不放在前言内。I.伯努利数的定义:万恶之源\(m\)次幂的求和公式1.伯努......