空间与时间的超级优化----背包问题的滚动数组

动态规划(背包问题)作为NOIP格外青睐的必考知识点之一,其根本原理逃不掉01背包。

暴力不好吗,打个屁DP

但是01背包的2维实现的思路简单,空间,时间复杂度都格外的大,尤其是n的取值范围在10的6次方以上,一般来说TLE是跑不掉的

打暴力吧

所以便有了01背包的滚动数组优化,但是优化必须在基础之上实现,并且以后的完全背包也是滚动数组的倒退。

所以先从2维讲起。尽量让新手明白

基础概念:

动态规划(Dynamic Programming 简称DP)是解决“多阶段决策问题”的一种高效算法。

通过合理组合子问题的解从而解决整个问题解。其中的子问题并不是独立的,这些子问题又包含有公共的子子问题

动态规划算法就是对每个子问题只求一次,并将其结果保存在一张表中(数组),以后再用到时直接从表中拿过来使用,避免重复计算相同的子问题。

1.划分阶段
2.确定状态和状态变量
3.确定决策并写出状态转移方程。
4.寻找边界条件。
5.编程实现程序(正推或倒推)。

例题:一本通1267–01背包问题(纯例题)

【题目描述】

一个旅行者有一个最多能装 M 公斤的背包,现在有 n 件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别为C1,C2,…,Cn,求旅行者能获得最大总价值。

【输入】

第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);

第2…N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

【输出】

仅一行,一个数,表示最大总价值。

【输入样例】

10 4
2 1
3 3
4 5
7 9

【输出样例】

12

分析

定义一个数组发f[N][N],f[i][j]表示对于第1~i个物品放在容量为j的背包中的最大价值;

考虑一下,f[i][j]是怎么来的?

必然由f[i-1][j] (表示不放的当前物品的最大值)和f[i-1][j-w[i]]+c[i] (表示放当前物品的最大值)的最大值

得出我们的状态转移方程

 if(j >= c[i]) f[i][j] = max(f[i-1][j], f[i-1][j-c[i]]+w[i])//放的下当前物品时
       else f[i][j] = f[i-1][j];//放不下当前物品时
        }

加上我们的嵌套循环,完整代码:

#include <iostream>
#include <cstdio>
using namespace std;
#define M 1000
int f[M][M], ans;
int v, m, c[M], w[M];
int main() {
	scanf("%d%d", &v, &m);
	for(int i = 1; i <= m; i++) scanf("%d%d", &c[i], &w[i]);
	for(int i = 1; i <= m; i++)
		for(int j = 0; j <= v; j++) {
			if(j >= c[i]) f[i][j] = max(f[i-1][j], f[i-1][j-c[i]]+w[i]);
			else f[i][j] = f[i-1][j];
		}

	printf("%d\n", f[m][v]);
	return 0;
}

来个分割线,正文开始

-------------------------------------------------

这个是时候我们要想,当n大的离谱,而且需要更多的操作的时候,我们的二维数组显得非常乏力,(例如落谷的P7223,需要用到快速幂)所以出现了滚动数组;

还是刚才的例题,自始至终我们发现,我们表示的物品数量一直都是1i-1个,所以出现了新的思路—降维打击~

那么如何将维度缩减为一维数组呢?

定义一个一维数组 f[N];让 f[j] 表示原来的 f[i-1][j]

如果你想学好动态规划的话,建议把下面的话理解

其实就是说我来比较我上一轮的最大容量减去当前一轮物品容量所剩的容量的最大值加上当前物品的价值;

这样的话其实就找到了状态转移方程

代码:
f[j]=max(f[j],f[j-a[i]]+b[i]);	

别走,还没完,因为我们的数组是随时更新的,但是要保留上一轮的最大值,所以我们的内循环就必须是倒退的

为什么呢?
举例说明吧:

假设我们的内循环正着走,则当i=1时

f[4]=15,f[5]=15,f[6]=15,f[7]=15

按照我们的状态转移方程(w=4,s=15)

f[8]=max(f[8],f[4]+15)=max(0,15+15)=30;

显然这是错误的,物品i在f[4]中存储了一次,而根据状态转移方程物品i又装了一次,但是倒退则没有关系,因为f[8]判断的时候f[4]仍然为0,就不会错误了,而且保留了上一次的结果,不会被覆盖。

综上所述,我们的内循环必须要倒退:

for(int i=1;i<=n;i++)
	   for(int j=k;j>=a[i];j--)

完整代码:

#include<iostream>
using namespace std;
int  k,n,a[350],b[350],f[2000]={0};
int main(){
	cin>>k>>n;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
	for(int i=1;i<=n;i++)
	for(int j=k;j>=a[i];j--)
	f[j]=max(f[j],f[j-a[i]]+b[i]);	
	cout<<f[k];
	return 0;
}

滚动数组的应用很多,后面的最短路径的Floyed算法,也是利用滚动数组实现的

这样代码短了很多,而且时间与空间也节省出来了,以上就是动态规划–背包问题的超级优化—滚动数组

有时间讲一讲Floyed算法 不可能的

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值