Class & Homework - DP 2

这次看一下经典的背包问题

入门: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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值