zoukankan      html  css  js  c++  java
  • 01背包问题学习笔记

    更好的阅读体验

    背包问题是DP动态规划算法中比较经典的一类模型,在NOIP考场上不定期地上位,令人琢磨不透,但是一旦学会了他,你就可以在短短十分钟的时间里,切掉他,达到节约时间,而且一次AC的目的.——某位大佬

    01背包 完全背包 多重背包 分组背包 混合背包
    对于物品而言只能选择1个或者0个两种情况 对于物品而言可以无限制选取,也可以不选 对于物品而言最多能够选择从s[i]个,同样也可不选 一些物品捆绑在一起,每一组物品中只能选择其中的一个物品 有些物品可以选择1,有些物品可以选择无数个,有些物品只能选择是s[i]个.即:01背包+完全背包+多重背包.
    滚动数组 滚动数组 滚动数组 滚动数组 滚动数组
    暂无 暂无 二进制优化或者单调队列优化 暂无 其中多重背包部分参考多重背包优化

    以上就是本次的思维导图,即学习框架.
    首先我们以01背包,所有背包的基础,开始进行本次的学习!

    01背包

    题目链接
    (N) 件物品和一个容量是 (V) 的背包。每件物品只能使用一次。

    (i) 件物品的体积是 (v_i),价值是 (w_i)

    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
    输出最大价值。

    输入格式
    第一行两个整数,(N,V),用空格隔开,分别表示物品数量和背包容积。

    接下来有 (N) 行,每行两个整数 (v_i, w_i),用空格隔开,分别表示第 (i) 件物品的体积和价值。

    输出格式
    输出一个整数,表示最大价值。

    数据范围
    (0 lt N, V le 1000)
    (0lt v_i, w_i le 1000)

    输入样例

    4 5
    1 2
    2 4
    3 4
    4 5
    

    输出样例:

    8
    
    朴素思路

    首先这道题目很明显的思路就是,对于一个物品而言,我们只有两种选择.

    1. 可以选择将他放入背包
    2. 不选择他,不放入背包中.
      那么一个朴素思路就是,我们可以使用二进制枚举,枚举每一个物品,我们到底是,选择这个物品,还是不选择这个物品.
      时间复杂度(O(2^N))
    DP思想 (O(n imes m))

    对于上面所说的复杂度,我们显然是不满意的,那么既然如此的话,我们如何去优化他呢?我们会想到动态规划(DP),因为这道题目有几个特殊的地方:

    1. 我们选择第i件物品,对于后面的物品并没有任何影响,即无后效.
    2. 它的决策非常明显,也就是我们只有,选择这个物品 或者 不选择这个物品这两种方案.

    综上所述,我们可以先考虑考虑DP算法.
    那么现在我们就要开始动态规划算法的思考题目流程.

    1. 状态数组如何定义
    2. 阶段是什么
    3. 决策是什么
    4. 边界是什么
    5. 状态转移方程
    状态数组的定义

    状态数组的定义:往往就是题目要我们求解的最终答案,所以我们大致可以这么定义状态数组.
    对于(f[i][j]),它表示为,在前i个物品选取若干个,已经花费的容量为j的最大利润.
    举个例子,(f[3][5])表示为在前三个物品中,我们选取0或者1或者2或者3个,然后已经花费了5点容量的最大利润.
    如果觉得例子解释不好,可以不看,害怕误解

    阶段

    根据我们的状态数组,我们可以这么选择阶段,也就是(1 le i le n),就是以物品为阶段.

    决策

    决策很明显,就是选取或者不选选取

    边界条件

    f数组全部清空就好了,因为我们要求的是最大利润.

    状态转移方程
    f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i];
    

    (f[i-1][j])就是前i-1个物品,花费j个容量的最大利润,在这里我们可以理解为,不选取第i个物品(即选取前i-1个)的最大利润.
    (f[i][j-v[i]]+w[i])就是选取第i个物品,花费为j个容量的最大利润,那么为什么要这么写?
    因为我们要放入这个物品,那么至少我们得需要(v[i])个容量,所以我们只能从(j-v[i])这个容量推过来,不然我们放不下这个背包.
    (+w[i])很明显就是选取了这个物品,当然要将这个物品的价值加上去.

    滚动数组优化

    第一步优化:
    首先,对于状态数组而言,我们只利用了(f[i][j])(f[i-1][j]).如下面这个例子所示

    1. 当i为1的时候,我们利用的是(f[0][j])(f[1][j])
    2. 当i为1的时候,我们利用的是(f[1][j])(f[2][j])
    3. 当i为1的时候,我们利用的是(f[2][j])(f[3][j])

    所以其实我们只需要利用(f[0][j])(f[1][j])就好了,0实际上表示i-1,1实际上表示i.
    第二步优化:
    实际上我们的j不一定要从(v[i],1+v[i],2+v[i]...m),而是可以从(m,m-1.m-2...v[i])因为这样的话,我们就可以从(f[2][n])(f[n])了.因为刚开始没有处理第二种决策的时候,(f[1][j])肯定等于(f[0][j]),然后我们从m到(v[i]),开始从上到下开始决策,就可以忽略(f[0])这一维了.
    这里讲的不太好,建议还是自行模拟一遍吧= ̄ω ̄=

    具体代码实现
    #include <bits/stdc++.h>
    using namespace std;
    const int N=1100;
    const int M=2e5;
    int n,m,i,j,k,a[N],f[M];
    int main()
    {
    	ios::sync_with_stdio(false);
    	cin>>m>>n;//读入最大容量和物品个数
    	for(int i=1;i<=n;i++)
    		cin>>a[i]; 
    	memset(f,0xcf,sizeof(f));//我这里是初始化为-INF,其实初始化为0就好了.
    	f[0]=0;
    	for(int i=1;i<=n;i++)//选取i个物品
    		for(int j=m;j>=a[i];j--)//容量设定
    			f[j]=max(f[j],f[j-a[i]]+a[i]);//转移
    	int ans=0;
    	for(int i=0;i<=m;i++)//f[n][i]表示选取n个物品,消耗容量为i的最有价值.
    		ans=max(ans,f[i]);
    	cout<<ans;//最优解
    	return 0;
    }
    
  • 相关阅读:
    细说Cookie(转)
    Custom Exception in ASP.NET Web API 2 with Custom HttpResponse Message
    内核中的定时器
    ibus拼音安装_ubuntu10.04
    linux模块
    zebra/quagga线程分析
    vim常用配置
    rar安装和使用
    zebra/quagga
    netsnmp编译动态库
  • 原文地址:https://www.cnblogs.com/gzh-red/p/11011573.html
Copyright © 2011-2022 走看看