01背包
01背包
引言
首先,
在拿到这道题的时候,我相信大部分人都会对这些物品的平均价值
我们假如有这样一个数据:
背包最大承受重量:10
物品1重量:7 物品1价值:20
物品2重量:4 物品2价值:10
物品3重量:5 物品3价值:11
这时,如果按照我们刚刚写出的贪心,物品一的平均价值约为
那我们的贪心哪里出问题了呢?这很简单,问题出在题面的物品不可分割上。不可分割,就意味着我们需要把整个物品都放进背包。里这时如果物品很大,背包将会剩余很多的空间。而这时如果我们选择平均价值较小,但重量也较小的多个物品放入背包,可能因为我们浪费的空间变小了,反而我们的总价值还变大了。
那我们是不是贪心的时候还要分析平均价值次小的物品呢?不,如果我们这样分析,我们就会发现还是不行。于是我们就会继续去分析平均价值次次小的物品,次次次小的物品.....这样我们就落到了一个陷阱里。这时,我们就需要
讲解
注意
接下来,我将用
a_i 来表示第i 个物品的重量,用b_i 来表示第i 个物品的价值,f(i,j) 表示如果有i 个物品,背包的重量为j 时题目的答案以下例题: P1048 [NOIP2005 普及组] 采药
我们先看看这道题的状态转移方程
其中,
可能听起来有点模糊,我以上面的例子列一个表格就明白了
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 20 |
20 |
20 |
20 |
| 2 | 0 | 0 | 0 | 0 | 10 |
10 |
10 |
20 |
20 |
20 |
20 |
| 3 | 0 | 0 | 0 | 0 | 10 |
11 |
11 |
11 |
11 |
21 |
21 |
我们以
看懂了吗?看不懂也没办法了
我们可以看出,如果我们的DP没有边界(也叫初始值),是没法计算的。因为其他答案的递推都要依赖以前的答案。这道题目中DP的边界就是当
其他见代码。
#include <bits/stdc++.h>
using namespace std;
int a[1005],b[1005];
int ans[1005][1005]; //同f
int main(){
int t,m;
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i];
}
for(int i=1;i<=m;i++){
for(int j=1;j<=t;j--){
if(j>=a[i]){ //为了防止下标出现小于0的情况
ans[i][j]=max(ans[i-1][j-a[i]]+b[i],ans[i-1][j]);
}else{
ans[i][j]=ans[i-1][j];
}
}
}
cout<<ans[m][t];
//system("pause");
return 0;
}
我们可以发现,
#include <bits/stdc++.h>
using namespace std;
int a[1005],b[1005];
int ans[1005]; //这里优化掉了一维
int main(){
int t,m;
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i];
}
for(int i=1;i<=m;i++){
for(int j=t;j>=1;j--){ //注意这里
if(j>=a[i]){
ans[j]=max(ans[j-a[i]]+b[i],ans[j]);
} //有没有发现少了一点东西?这是因为去掉了第一维后ans[i][j]=ans[i-1][j]相当于ans[j]=ans[j],已经没有用了
}
}
cout<<ans[t];
//system("pause");
return 0;
}
完全背包
以下例题
P2722 [USACO3.1] 总分 Score Inflation
在上文
的答案都是在背包可承载重量为 $j$ 时尽可能放入第 $i$ 个物品,导致第 $i$ 个物品被重复放置,变成一 个完全背包。
所以,我们把状态转移方程改一改,就成了这样
那有人会说了:你怎么知道如果把
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 20 |
20 |
20 |
20 |
| 2 | 0 | 0 | 0 | 0 | 10 |
10 |
10 |
20 |
|||
| 3 | 0 | 0 | 0 | 0 | 10 |
11 |
11 |
20 |
21 |
22 |
在这个表格中,我们的答案
如果没看懂,就自己照着状态转移方程模拟几遍,总是会看懂的
代码如下:
#include <bits/stdc++.h>
using namespace std;
int a[30005],b[30005],c[30005];
int ans[50000][50000];
int main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=k;i++){
cin>>a[i]>>b[i];
}
for(int i=1;i<=k;i++){
for(int j=b[i];j<=n;j++){
ans[i][j]=max(ans[i][j-b[i]]+a[i],ans[i-1][j]);
}
}
cout<<ans[k][n];
//system("pause");
return 0;
}
如果你把这个代码直接交上去,会发现编译失败了
这是因为数组开大了,但如果你把数组改小以后,也会RE
于是我们有需要我们的老朋友了——滚动数组
观察上文的转移方程,可以发现
#include <bits/stdc++.h>
using namespace std;
int a[30005],b[30005],c[30005];
int ans[50000];
int main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=k;i++){
cin>>a[i]>>b[i];
}
for(int i=1;i<=k;i++){
for(int j=b[i];j<=n;j++){ //注意起始条件和终止条件
ans[j]=max(ans[j-b[i]]+a[i],ans[j]);
}
}
cout<<ans[n];
//system("pause");
return 0;
}
不知道你们有没有注意到,这个滚动数组是从
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 20 |
20 |
20 |
20 |
|
| 2 | 0 | 0 | 0 | 0 | 10 |
10 |
10 |
20 |
||||
| 3 | 0 | 0 | 0 | 0 | 10 |
11 |
11 |
20 |
21 |
22 |
如果你与前面的代码比较,就会发现这里
在这里,我们就是要运用 数据被覆盖 这个东西,来让
带附件的背包
以下例题
P1064 [NOIP2006 提高组] 金明的预算方案
在上面的背包问题中,我们的物品只有主件,但在带附件的背包中,我们还会带着一些附件。当我们要 买主件时,不一定要买附件,但如果要买附件,就必须要买主件。 所以我们在 DP 时,就有了更多的选择
接下来,我将用
tai ,j 来表示第i 个物品的第j 个附件的重量,用tbi ,j 来表示第i 个物品的第j 个 附件的价值,用tci 来表示第i 个物品的附件的个数
这类题目目的状态转移方程:
在这道题中,附件最多有