[动态规划] 01背包

有N个物品,需要装进大小为M的背包中,每件物品的大小和价值为w和m,求能得到的最大价值。

输入

        第一行为N和M

        之后N行为第i个物品的W和M

输出

        最大价值

案例:

5 10
5 3
3 6
7 8
5 9
2 4

dp[i][j]表示:    考虑前 i 个物品,总大小为 j 的最大收益


递推公式:
        对于第 i 个物品,大小为 j 时的最大收益

        不满足v[ i ]则不取,即 dp[ i-1 ][ j ]
        取        dp[ i-1 ][ j-v[i] ] + w[ i ]        //前 i-1 个物品大小为j-v[ i ]的最大收益+当前物品收益
        不取    dp[ i-1 ][ j ]                         //前 i-1 个物品大小为 j 的最大收益

初始化:

        每个dp[ i ][ j ] 和 前 i-1 有关

        当 i = 1 时,对于 i = 0,全为 0 不影响结果

        所以全部初始化为 0 即可

遍历顺序

        每个dp[ i ][ j ] 和 前 i-1 有关

        所以需要外层 i 从 1 到 n

        内层 j 从 (1 到 m) 或 (m 到 1)都可以

打印

        即为dp[ i ][ j ]

对于打印为什么不是从dp[ i ]中选取最大值?

因为刚开始全部初始化为 0 ,而空间最大为最大的值



int n,m,dp[1001][1001],v[1001],w[1001];

int main(){
    //输入
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d%d",&v[i],&w[i]);
	//dp
	for(int i=1; i<=n; i++)
		for(int j=0; j<=m; j++)
			if(j<v[i])                    //如果 j 比 v[i] 的空间还小
				dp[i][j] = dp[i-1][j];
			else                        
				dp[i][j] = max(dp[i-1][j-v[i]] + w[i], dp[i-1][j]);
	//输出
	for(int i=1; i<=n; i++){
		for(int j=0; j<=m; j++)
			printf("%d\t",dp[i][j]);
		printf("\n");
	}
	
	return 0;
}

输出结果:

0       0       0       0       0       3       3       3       3       3       3
0       0       0       6       6       6       6       6       9       9       9
0       0       0       6       6       6       6       8       9       9       14
0       0       0       6       6       9       9       9       15      15      15
0       0       4       6       6       10      10      13      15      15      19

我们发现该代码所需空间太大复杂度为O(NM)。我们可以继续观察到每次我们更新的第 i 列dp数组只与第 i-1 列的数组有关,所以可以考虑滚动数组,压缩空间。

这里我们使用两个数组,d1 和 d2 分别代表每次的 上一列 和 这一列 。

int n,m,dp[1001][1001],v[1001],w[1001];
int d1[1001],d2[1001];

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d%d",&v[i],&w[i]);
	//dp
	for(int i=1; i<=n; i++){
		for(int j=0; j<=m; j++)
			if(j<v[i])
				d2[j] = d1[j];
			else
				d2[j] = max(d1[j],d1[j-v[i]]+w[i]);
		memcpy(d1,d2,sizeof(d2));                    //将d2数组的值赋予d1
	}
	return 0;
}

d2 数组的每一行更新后为:

0 0 0 0 0 3 3 3 3 3 3
0 0 0 6 6 6 6 6 9 9 9
0 0 0 6 6 6 6 8 9 9 14
0 0 0 6 6 9 9 9 15 15 15
0 0 4 6 6 10 10 13 15 15 19

空间复杂度为O(m)!我们能不能用O(N)的复杂度呢?

我们每一次更新第 i 行 j 个时,只与第 i - 1 行的 第 j 个或者第 j-v[i]个有关

因此我们可以每次从后往前依次更新一个数组

代码如下:

int n,m,dp[1001],v[1001],w[1001];

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=m; j>=v[i]; j--)
			dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
		
        //输出每一行
        for(int j=0;j<=m;j++)
			printf("%d ",dp[j]);
		printf("\n");
	}
	return 0;
}

 关于 j 的边界,之前从前往后遍历是 j < v[ i ]的时候不更新,所以这个时候要到j >= v[ i ]即可结束

如果第 i 行的 v[ i ] 比第 i-1 个大,那么前面会有一部分没有更新,但是这一段的 dp 的值在之前的代码 j < v[ i ] 时:  dp[i][j] = dp[i-1][j]   即当前代码还是上一行的,也就是可以不用更新。所以所得的值最终与第一版相同

0 0 0 0 0 3 3 3 3 3 3
0 0 0 6 6 6 6 6 9 9 9
0 0 0 6 6 6 6 8 9 9 14
0 0 0 6 6 9 9 9 15 15 15
0 0 4 6 6 10 10 13 15 15 19

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值