这次看一下经典的背包问题
入门:Charm Bracelet // poj 3624 oj 4131 【0-1背包】
0-1背包的特点是,物品只能选一次,因此对于每个物品,只有选或不选两种可能
由此,做一个基本的DP
#include<iostream>
using namespace std;
int N,M;
struct charm{
int w;
int d;
}c[3405];
int dp[3405][12882]={0}; //前i个挂坠重量不超过j可以实现的最大D
int main(){
scanf("%d%d",&N,&M);
for(int i=1;i<=N;++i) scanf("%d%d",&c[i].w,&c[i].d);
for(int i=1;i<=N;++i) for(int j=1;j<=M;++j){
if(j>=c[i].w) dp[i][j]=max(dp[i-1][j-c[i].w]+c[i].d, dp[i-1][j]);
else dp[i][j]=dp[i-1][j];
}
cout<<dp[N][M]<<endl;
return 0;
}
但是,这样占用的空间极大,而且也容易超时。
我们观察这个dp数组的结构,发现,对于每个(i, j),只调用了dp[i-1]中 j-w[i] 和 j,也就是正上方和左边。
利用“滚动数组”,可以改写成关于 j 的一维数组。
注意:j 应逆向循环,因为我们要保留上方和左边的部分,所以从右边开始循环。
#include<iostream>
using namespace std;
int N,M;
struct charm{
int w;
int d;
}c[3405];
int dp[12882]={0}; //(前i个挂坠)重量不超过j可以实现的最大D
int main(){
scanf("%d%d",&N,&M);
for(int i=1;i<=N;++i) scanf("%d%d",&c[i].w,&c[i].d);
for(int i=1;i<=N;++i) for(int j=M;j>=c[i].w;--j){
dp[j]=max(dp[j-c[i].w]+c[i].d, dp[j]);
}
cout<<dp[M]<<endl;
return 0;
}
入门:神奇的口袋 // oj 2755
0-1背包的变形,实际上更简单
①递归
#include<iostream>
#include<algorithm>
using namespace std;
int a[42],n;
int cnt;
void f(int i,int v){ //物品编号,剩余体积
if(v<=0){
if(v==0) ++cnt;
return;
}
for(int j=i+1;j<=n;++j) f(j,v-a[j]);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",a+i);
cnt=0;
for(int i=1;i<=n;++i) f(i,40-a[i]);
cout<<cnt<<endl;
return 0;
}
②“人人为我”递推
#include<iostream>
#include<cstring>
using namespace std;
int a[42],n;
int dp[22][42]; //用前i个物品组成重量j的方案数
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",a+i);
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i) for(int j=1;j<=40;++j){
dp[i][j]=dp[i-1][j];
if(j>a[i]) dp[i][j]+=dp[i-1][j-a[i]];
else if(j==a[i]) dp[i][j]++; //一般做法是提前将a[i][0]都处理成1,此处仅为在下的个人偏好
}
printf("%d\n",dp[n][40]);
return 0;
}
进阶:A decorative fence // oj 1037