01背包问题
1.问题重述
题目
有n个重量和价值为wi,vi的物品,从这些物品中挑选出总重量不超过M的物品,求所有可选方案中价值总和的最大值。
1<=n<=100
1<=wi,vi<=100
1<=W<=10000
输入:
n=4
(w,v)={{2,3},{1,2},{3,4},{1,1}}
W=5
输出:
7(选择0,1,3号物品)
2.采用dfs深度优先搜索
//和子集、全排列很像 ,选还是不选
//用dfs解决
#include<bits\stdc++.h>
using namespace std;
//全局不需要传参
int w[]={2,1,3,2};//重量表
int v[]={3,2,4,2};//价格表
int n=4;
int W=5;
int rec[5][6];
int dfs(int i,int ww)//从i号物品开始选 ,ww为还可以装物品重量
{
int v1,v2,ans;
if(ww<0) return 0;//装不进去
if(i==n) return 0;
v2=dfs(i+1,ww);//不选当前物品
if(ww>=w[i]){
v1=v[i]+dfs(i+1,ww-w[i]);//选择当前物品
return max(v1,v2);
}else{
return v2;
}
}
int main()
{
int ww=W;//保存变量W
memset(rec,-1,sizeof(rec));//初始化全为-1
int ans=dfs(0,ww);
//int ans=dfs1(0,ww);
cout<<ans<<endl;
return 0;
}
//出现了重叠子问题
//解决方法:记忆形递归,带备忘录
//rec[][]增加速度很多
改进:增加记忆化搜索,加快搜索速度
int dfs1(int i,int ww)//从i号物品开始选 ,ww为还可以装物品重量
{
int v1,v2,ans;
if(ww<0) return 0;//装不进去
if(i==n) return 0;
//1.计算之前先查询
v2=dfs(i+1,ww);//不选当前物品
if(ww>=w[i]){
v1=v[i]+dfs(i+1,ww-w[i]);//选择当前物品
ans=max(v1,v2);
}else{
ans=v2;
}
//2.计算之后做保存
rec[i][ww]=ans;
return ans;
}
3.采用动态规划dp求解
用excel找规律(dp问题要理解表格),算法根据填表逻辑写;
找出变化的量:容量变化、物品范围变化、价格变化;
两个自变量(包的容量,物品选哪些),一个因变量(价格)
画出二维表
(w,v) | 物品编号(当前编号以及之前的范围) | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|---|
(2,3) | 0 | 0 | 0 | 3 | 3 | 3 | 3 |
(1,2) | 1 | 0 | 2=max(要:2+0,不要:0) | 3=max(要:2+0,不要:3) | 5=max(2+3,3) | 5=max(2+3,3) | 5=max(2+3,3) |
(3,4) | 2 | 0 | 2 | 3 | 5=(4+0,5) | 6=(4+2,5) | 7=(4+3,3) |
(2,2) | 3 | 0 | 2 | 3=(2+0,3) | 5=(2+3,5) | 6=(2+3,6) | 7=(2+5,7) |
最右下角的单元格即为所求
从历史数据处理得出结果 :
我不要这个物品:找上方的单元格
要的话:本物品价格+减去当前物品容量的上一行单元格对应的价格
根据表格写出算法如下:
int dp()
{
int dp[n][W+1];
//设置边界
for(int i=0;i<W+1;i++){
if(i>=w[0]){//每种容量和我们的0号物品比较,大的话就可以装进
dp[0][i]=v[0];
}else{
dp[0][i]=0;//初始化dp表的第一行
}
}
//其他行
for(int i=1;i<n;i++){
//j是列,也是背包的剩余容量
for(int j=0;j<W+1;j++){
if(j>=w[i]){//要的起
int v1= v[i]+dp[i-1][j-w[i]];//选择当前物品即i号物品,剩余容量
int v2=dp[i-1][j];
dp[i][j]=max(v1,v2);
}else{//要不起
dp[i][j]=dp[i-1][j];
}
}
}
return dp[n-1][W];
}
//递归要等子问题结果
//dp不用等,直接用已有问题
int main()
{
int ans=dp();
cout<<ans<<endl;
return 0;
}
附上oj上答案(有些不同)
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
int Max(int a,int b)
{
return a>b?a:b;
}
int f[1001][1001];
int main()
{
int t,n,v;
int N[1001],V[1001];
cin>>t;
while(t--)
{
cin>>n>>v;
for(int i=1;i<=n;i++)
{
cin>>N[i];//价值
}
for(int i=1;i<=n;i++)
{
cin>>V[i];//体积
}
memset(f,0,sizeof(f));//把f数组清零
//和上述算法不同,边界是从第一列考虑的
for(int i=1;i<=n;i++){
for(int j=0;j<=v;j++){
if(j>=V[i])//可从f[i-1][j-V[i]]反推,若j-V[i]<0索引出错
f[i][j]=Max(f[i-1][j],f[i-1][j-V[i]]+N[i]);
else f[i][j]=f[i-1][j];
}
}
cout<<f[n][v]<<endl;
}
return 0;
}
小建议:希望把用dp算法解决的问题的数组名都改为dp