ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

3. 完全背包问题

2021-10-06 10:04:40  阅读:110  来源: 互联网

标签:选择 背包 10 int max 完全 问题 物品


题目传送门

一、完全背包模板

把\(01\)背包的代码,容量循环的顺序变成由小到大即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];


//完全背包问题
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];

    for (int i = 1; i <= n; i ++ )
        for (int j = v[i]; j <= m; j ++ ) //一个一个加上来,求一个最大值
            f[j] = max(f[j], f[j - v[i]] + w[i]); 
    cout << f[m] << endl;
    return 0;
}

二、01背包与完全背包的本质差别

状态表示:\(f(i,j)\)
集合: \(i\)是指在前\(i\)个物品中进行选择,\(j\)是指在总体积不超过\(j\)的容量下进行选择。
属性: 求一个最大值。 因为按上面的集合进行选择,每选择一次,就会出现一个结果,我们想求的是这些结果中的最大值。

状态计算
与\(01\)背包不一样,\(01\)背包是每一个物品选与不选,完全背包不是这个意思。是可以不选,选择\(1\)个,或者选择多个。

就是按选择\(0-k\)个物品,划分成了\(k+1\)个集合,原来两个选与不选,到这里就变成了\(k+1\)种方案,我们需要逐个讨论才能完整描述事实。

\(f(i,j)=max(f(i-1,j),f(i-1,j-v_i)+w_i,f(i-1,j-2*v_i)+2w_i,....\)

上面的公式简单理解一下,就是第\(i\)个物品
(1) 可以不取:\(f(i-1,j)\)
表示还在前\(i-1\)个物品中选择,第\(i\)个不参加选择,由于没有选择,所以空间还剩余\(j\)这么大。

(2) 选择1个:\(f(i-1,j-v_i)+w_i\)
表示选择了一个第\(i\)个物品,其它的都需要从前\(i-1\)个物品中选择,由于使用了一个\(i\)物品,所以袋子的容量减少\(v_i\),剩余 \(j-v_i\),同时,价值增加了\(w_i\).

(3) 选择\(2\),\(3\),\(4\)...都与选择\(1\)是一样的,一直到选择\(k+1\)个\(i\)号物品,使得体积大于了剩余容量,就不能继续了。

至此,我们已经得到了状态转移方程,是可以直接用来求解,方法就是三重循环,伪代码如下:

\(for(i=1;i<=n;i++)\) 物品
\(\qquad\) \(for(j=1;j<=m;j++)\) 体积
\(\qquad\) \(\qquad\) 在前\(i\)件物品的前提下,在\(j\)限定的空间里,遍历所有可有存在的个数
\(\qquad\) \(\qquad\) \(\qquad\) 求出最大值

这样做,需要三重循环,本题给出的\(n,m\)的取值范围是1000,三重循环的时间复杂度就是\(10^3 * 10^3 * 10^3=10^9\),而\(c++\)的一秒能计算\(10^7\)到\(10^8\),所以,会超时,需要继续优化。

那么如何进行优化呢?数学是个好东西,现在让数学大神出场:

已知:
\(f(i,j)=max(f(i-1,j),f(i-1,j-v_i)+w_i,f(i-1,j-2 \times v_i)+2w_i,....\)

如果\(j=j-v_i\)时,代入上式:

\(f(i,j-v_i)=max(f(i-1,j-v_i),f(i-1,j-2\times v_i)+w_i,f(i-1,j-3 \times v_i)+2w_i,....\)

新产生的式子,和第一个式子是很像的,但每项缺少一个\(w_i\),将新产生的式子代入最上面的式子:就是
$f(i,j-v_i)+w_i=max(f(i-1,j-v_i)+w_i,f(i-1,j-2\times v_i)+2 * w_i,f(i-1,j-3 \times v_i)+3 * w_i,.... $

\(f(i,j)=max(f(i-1,j),f(i,j-v_i)+w_i)\)

太神奇了!!一般同学们学习完全背包问题时,都喜欢直接看状态转移方程来理解含义,结果发现理解不上去。为什么会发生这样的事情呢?原因很简单,因为人家根本不是从现实直接过度到方程的,人家中间是有一顿推导过程的,你略过了中间过程,肯定看结果看不懂了,啥叫循序渐进?啥叫学个通透?要知其然,并知其所以然才能学的精通。

总结一下
二维情况下的状态转移方程

0 $\ $1背包 : \(f[i][j]=max(f[i-1][j],f[i-1][j-v_i]+w_i)\)
完全背包: \(f[i][j]=max(f[i-1][j],f[i][j-v_i]+w_i)\)

这样,再用二维除一维的思路来思考,就得到了完全背包的终极解法:

 for (int i = 1; i <= n; i ++ )
        for (int j = v[i]; j <= m; j ++ ) 
            f[j] = max(f[j], f[j - v[i]] + w[i]); 

C++ 代码【二维朴素写法】

#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N]; 
//枚举体积有限制是在优化后的算法有限制,在两维时候想怎么循环就怎么循环,是没有限制的
//为什么优化后要有限制呢??
//这是因为在二维里,有一个是不是剩余容量能装得下当前物品的判断,在优化的版本去掉了这句话,把它放到循环条件中了。

//完全背包问题
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];

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

C++ 代码 【一维终极解法】

#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];


//完全背包问题
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];

    for (int i = 1; i <= n; i ++ )
        for (int j = v[i]; j <= m; j ++ ) //一个一个加上来,求一个最大值
            f[j] = max(f[j], f[j - v[i]] + w[i]); 
    cout << f[m] << endl;
    return 0;
}

三、思考

为什么01背包要倒着推,完全背包要顺着推
https://www.cnblogs.com/MyNameIsPc/p/7782700.html

标签:选择,背包,10,int,max,完全,问题,物品
来源: https://www.cnblogs.com/littlehb/p/15370457.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有