目录
简单背包问题:
一、题目来源:
二、解析:
这个题是最简单的一类背包问题。题目要求一个理想的结果(找到最大价值)。
一个物品有两个性质:一是所占的体积,二是所拥有的价值,我们可以利用两个一维数组来分别存储这两个性质,利用一个二维数组把选与不选这些物品时的最优解存起来。
这里还需要用到一个知识,就是动态规划,这是一个很重要又很难的一个思想,其中最重要的就是找状态方程,也就是一个关系式。那么接下来我们来找一下这个关系:
这里n这一列代表第几个物品,v这一列代表第n个物品的体积,w这一列代表第n个物品的价值;
中间就是二维数组,其中当n为0时没有意义,可以去掉,在输入时从1开始即可,每一列表示还剩多少体积时的最优解,每一行表示选与不选这个物品的最优解。
先分析一下第一行:
当剩余体积为0时,我们肯定放不下去任何东西,所以第一个为0(价值);
当剩余体积为1时,只能放下去体积为1的物品,巧了,这一件物品的体积就是1,而且前面没有别的物品了所以这里就是它的价值2;
后面的以此类推;
当来到第二行的时候:
第一个空的分析同上,也就是和正上方的一格的值一样;
当来到第二个空的时候,剩余体积为2,此时 本物品的剩余体积正好为2,那我们就要考虑一下这一格里面是直接填上一格的值还是要改变(即把该物品的价值填入),很显然,我们需要一个最大的值;
由此接着分析下去,我们就得到了这个问题的一个状态方程:
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
到此,我们这个题就算解决了,剩下的就是代码实现。
三、代码实现:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int M = 2e3 + 10;
int n, m, f[M][M], v[M], w[M];//v[M]是体积,w[M]是价值
signed main()
{
cin >> n >> m;
int i, j;
for (i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for (i = 1; i <= n; i++)
{
for (j = 0; j <= m; j++)//对于二维数组的实现,我们需要每一行都全部遍历并改变
{
f[i][j] = f[i - 1][j];
//我们一开始不知道剩余体积怎么样,那我们就把上一个的值直接拉下来
//也就是说我们现在没选这个物品
if (j >= v[i])
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
//当我们知道此时剩余的体积可以容下该物品,那我们就要选这个物品并比较最优解
}
}
cout << f[n][m] << endl;
}
四、优化:
(I)滚动数组:
(II)一维数组:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int M = 2e3 + 10;
int n,m,f[M],v[M],w[M];
signed main()
{
cin>>n>>m;
int i,j;
for(i=1;i<=n;i++)
cin>>v[i]>>w[i];
for(i=1;i<=n;i++)
{
for(j=m;j>=v[i];j--)//这里从大到小遍历,保证每个物品被使用一次
{
if(j>=v[i])
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
}
五、推广:
这个问题可以推广到很多题目,比如采药问题(时间、价值)等等一系列具有以下特征的题型:
(1)一个物品有多种属性(常为两种);
(2)需要找到最优解;
......
完全背包问题:
一、题目来源:
二、分析:
完全背包问题和01背包问题的差别就是对于物品是否可以多次使用。
完全背包问题中的物品可以多次使用(可以无限次,只要不超过背包容量),而01背包问题中的物品不可以重复使用。
由于物品可以多次使用,所以我们在遍历的时候就不能对其进行从大到小的遍历(一维),而是进行从小到大遍历,保证物品可以多次使用。
三、代码实现:
#include <bits/stdc++.h>
using namespace std;
const int M = 2e3 + 10;
int n, m;
int v[M], w[M], f[M];
int main()
{
int i, j;
cin >> n >> m;
for (i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
for (i = 1; i <= n; i++)
{
for (j = v[i]; j <= m; j++)//这里从小到大遍历,保证一个物品可使用多次
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m];
return 0;
}
多重背包问题:
一:题目来源:
二、分析:
对于多重背包问题,与其他不同点在物品的个数不唯一,也相当于可以重复使用。但是与完全背包问题不同的是,多重背包问题中物品的使用次数有限。
对于这类问题,我们可以利用完全背包的形式去做,只要对物品使用次数进行记录和限制就可以。
我们也可以利用01背包问题的形式去做,只要把物品展开,将物品的数量变为1即可。
下面对这两种形式进行实现。
三、代码实现:
利用01背包问题形式:
#include <bits/stdc++.h>
using namespace std;
const int M = 2e6;//这里要注意开的数组长度的大小,有时可能会因为数组长度太小而WA掉
int n, m;
int v[M], w[M], f[M], s[M];
int main()
{
int i, j;
cin >> n >> m;
for (i = 1; i <= n; i++)
{
cin >> v[i] >> w[i] >> s[i];
}
// 将物品全部展开,数量为1,变成01背包问题,数量变为k
int k = n;
for (i = 1; i <= n; i++)
{
while (s[i] > 1)//由于物品的数量不确定,所以利用循环使物品全部展开
{
k++;
v[k] = v[i];
w[k] = w[i];
s[i] -= 1;
}
}
for (i = 1; i <= k; i++) // 现在是k个物品
{
for (j = m; j >= v[i]; j--)
{
if (j >= v[i])
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m];
return 0;
}
利用完全背包问题形式:
#include <bits/stdc++.h>
using namespace std;
const int M = 2e6;
int n, m;
int v[M], w[M], f[M], s[M];
int main()
{
int i, j, k;
cin >> n >> m;
for (i = 1; i <= n; i++)
{
cin >> v[i] >> w[i] >> s[i];
}
// 遍历物品
for (i = 1; i <= n; i++) // 现在是k个物品
{
// 遍历背包
for (j = m; j >= v[i]; j--)
{
for (k = 1; k <= s[i]; k++)//这里的k用来限制使用物品的次数
{
if (j >= k * v[i])
f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
}
}
}
cout << f[m];
return 0;
}