—01背包 完全背包先学。
代码随想录 笔记dp
1. 01背包问题
N件物品和一个容量为V的背包,每件物品只能使用一次。
求装入哪些物品,使得总价值最大,且不超过背包容量。
输出最大值
N=4,V=5
体积 | 价值 |
---|---|
1 | 2 |
2 | 4 |
3 | 4 |
4 | 5 |
dp[n][v] :
解释:v的体积限制下,决策前n个物品后,所获得的最大价值。
定义f[i][j]
表示从前 i 个物品中选择,体积为 j 的时候的最大价值。
一、当前背包余量够放物品 充足
1.放入
放入第n个物品,则产生的价值为
(n-1,V-v[n])状态下的最优值,加上第n个物品的价值 v[n]
即 dp[n-1][V-v[n]]+v[n]
2.不放入
不放入第n个物品,则产生的价值不改变,等于决策前n-1个物品的最优值
即dp[n-1][V]
取max
dp[n][v]=max{dp[n-1][V-v[n]]+v[n],dp[n-1][V]};
二、当前背包容器不够放入最后一个物品
那么dp[n][v]=dp[n-1][v]
回溯图解
一维数组优化
1.1 代码参考
#include <iostream>
using namespace std;
const int N=1010;
int v[N],w[N],f[N][N];
// v[N] 物品重量,W[N] 物品最大价值
int N,V;//n 物品数量 v 背包容量
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=N;i++) scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=N;i++)
for(int j=1;j<=V;j++)
{
// if(j>=v[i]){
//放的下第i件物品
//f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
//}else{
//当不下第i件物品
// f[j][j]=f[i-1][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]);
}
printf("%d",f[n][m]);
return 0;
}
void flashBack(){
int i=N,j=V;
// 从最后一个进行回溯
while(i!=0){
if(f[i][j]===f[i-1][j]){
// 说明相等,没有选中第i件物品
i--;
}else{
// 选择了第i件物品
list.push_back(i);
// 减去第i件物品的重量空间
j-=v[i];
i--;
}
}
}
void better_dp(){
//简化 从后往前遍历
for(int i=1;i<=N;i++)
{ for(int j=V;j>=v[i];j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
void knapsack(){
//边界处理
for(int i=0;i<=V;i++)fi[0][i]=0;
//状态更新
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
if(j>=v[i]){
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]+w[i]]);
}else{
f[i][j]=f[i-1][j];
}
}
}
}
}
状态方程
dp[n][v]:
剩余容量能够放入最后一个物品 :dp[n][v]=max{dp[n-1][V-v[n]]+v[n],dp[n-1][V]};
不够 dp[n][v]=dp[n-1][v]
状态压缩
dp[v]:
剩下的容量能够放入最后一个物品
dp[v]=max{dp[v-w[i]]+c[i],dp[v]} , v-w[i]>=0
不够
dp[v]=dp[v]
2. 多重背包问题
有n件物品,每件物品的重量为 w[i],价值为c[i]。现有一个容量为V的背包。
问如何选取物品放入背包,使得背包内的物品的总价值最大。其中每件物品有 s[i] 件。
输入数据描述
物品 | 重量 | 价值 | 数量 |
---|---|---|---|
1 | 3 | 2 | 2 |
2 | 4 | 3 | 2 |
3 | 2 | 2 | 1 |
4 | 5 | 3 | 4 |
当背包的容量V=10时,所能产生的最大价值?
2.1 朴素方法
对相同物品张开
物品 | 重量 | 价值 | 数量 |
---|---|---|---|
1 | 3 | 2 | 1 |
1 | 3 | 2 | 1 |
2 | 4 | 3 | 1 |
2 | 4 | 3 | 1 |
3 | 2 | 2 | 1 |
4 | 5 | 3 | 1 |
4 | 5 | 3 | 1 |
4 | 5 | 3 | 1 |
4 | 5 | 3 | 1 |
转化为 01 背包问题,数量已经没用了
物品一共有 ∑s[i],共有∑s[i]行。时间复杂夫 O(V*∑s[i])
2.2 二进制优化算法
多重背包算法代码-参考
二进制拓展行后的
物品 | 重量 | 价值 | 数量 |
---|---|---|---|
1 | 3 | 2 | 1 |
2 | 3 | 2 | 1 |
3 | 4 | 3 | 1 |
4 | 4 | 3 | 1 |
5 | 2 | 2 | 1 |
6 | 5 | 3 | 1 |
7 | 10 | 6 | 1 |
8 | 5 | 3 | 1 |
//多重背包问题
# include<stdio.h>
#include<iostream> using namespace std;
const int MAXN=10010;//定义最大物品数量
const int MAXV=10010;//定义最大背包容量
int N;// 物品数量 行拓展后的
int V;//背包容量
int w[MAXN];//储存每件物品的重量w[i]
int c[MAXN];//储存每件物品的价值c[i]
int s[MAXN];//储存每件物品的数量s[i]
int dp[MAXV];//滚动dp数组
//用滚动dp数组求解
void knapsack() { //边界处理
for (int i = 0; i <= V; i++)
dp[i] = 0;
//状态更新
for (int i = 1; i <= N; i++) {
//倒序枚举v(v—0)
for (int v = V; v >= w[i]; y--)
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
}
}
int main(){
//实际上每件物品的重量和价值
int tempw[MAXN],int tempc[MAXN];
int k=0;// 存储展开后的标号
cin>>N>>V;//物品个数和最大背包容量
//读取tempw[i] 实际的物品重量读入
for(int i=1;i<N;i++){
cin>>tempw[i];
}
// 读取物品 的价值
for(int i=1;i<N;i++){
cin>>tempc[i];
}
// 利用二进制优化方法拓展行
//读入物品件数
for(int i=1;i<N;i++){
cin>>s[i];
//每读取一次进行一次行拓展
//第一行1 二行2个 三行4个 2^n
// j=1 从第一行开始 j=1表示第一行数量为1 2^0
for(int j=1;j<s[i];j*=2){
k++;//k新数组
w[k]=tempw[i]*j;// 拓展后是重量
c[k]=tempc[i]*j;//拓展后的价值
s[i]-=j;
}
// 计算剩下的 s[i];
if(s[i]!=0){
k++;
w[k]=s[i]*tempw[i];
c[k]=tempc[i]*s[i];
}
}//endfor
N=k;//实行拓展后实际物品件数
knapsack();
cout<<dp[V];
}
3.完全背包问题
3.1公式推导
f[i , j ] = max( f[i-1,j] , f[i- 1,j - v[i]+ w[i] , f[i - 1,j-2 * v[i]]+2 * w[i] , f[i -1,j - 3 * v[i]]+3 * w[i] , …)
f[i , j - v[i]]= max( f[i - 1,j - v[i]] , f[i - 1,j - 2 * v[i]] + w[i] , f[i - 1,j- 3 * v[i]]+2 * w[i] , …)
f[i][j-v[i]]+w=f[i][j]