背包九讲之递推算法 一(01背包)

发布于:2022-11-28 ⋅ 阅读:(584) ⋅ 点赞:(0)

目录

前言

一、什么背包问题

二.01背包

2.1我们通过进行遍历i次,与决策j *i次来选择出状态最优解

2.2简单来说,就是f[j]需要用到前面计算的数据已经被无意地篡改了。

2.3 优化输入

总结


前言

记录一下初学算法的灵光


一、什么背包问题

简单来讲,就是把物品放到背包里的动态规划问题。

例:

有 N 件物品和一个容量是 V 的背包。

第 i件物品的体积是 v[i],价值是 w[i]。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

 下面来浅谈一下该种问题的算法分析。

二.01背包

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i件物品的体积是 v[i],价值是 w[i]。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

2.1我们通过进行遍历i次,与决策j *i次来选择出状态最优解

如果背包容量不够

该决策不变等于上轮决策结果

对应代码:f[i][j] = f[i - 1][j]

当前背包容量够,可以选,因此需要决策选与不选第 i个物品:

选 :f[i][j] = f[i - 1][j - v[i]] + w[i]

不选:f[i][j] = f[i - 1][j]

我们的决策是如何取到最大价值,因此以上两种情况取 max() 

#include <iostream>
using namespace std;
int v[1005];
int w[1005];
int f[1005][1005];
int main()
{
	int n, m,i,j;
	scanf("%d%d", &n, &m);
	for (i = 1; i <= n; i++)
		scanf("%d%d", &v[i], &w[i]);
	for (i = 1; i <= n; i++)//判断n次
		for (j = 1; j <= m; j++)//遍历使用1—m个空间,判断可否存放该次i
			if (j < v[i])//该空间大小放不下
				f[i][j] = f[i - 1][j];//等于上一次能放下的情况
			else
				f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);//选出上次与这次的大值:f[i-1][j-v[i]]+w[i]存放i剩的空间能放的最大值+本次值
	printf("%d", f[n][m]);
}

 但显而易见,该方法有个小缺点L:二维数组每一次决策(f [ i ][ j ])都是只与上一组(f [ i -1 ][ j ])

进行数据使用及比较,这就很容易造成资源内存的浪费

或许,我们可以试着将 f 数组从二维降到一维...

​
#include <iostream>
using namespace std;
const int N=1000;
int f[N],v[N],w[N];
int main()
{
    int m,n,i,j;
   scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
    scanf("%d%d",&v[i],&w[i]);
    for(i=1;i<=n;i++)
    for(j=1;j<=m;j++)
        if(j<v[i])
        f[j]=f[j];
        else
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    printf("%d",f[m]);
}

​

 高贵的食材往往只需要最朴素的烹饪方式

我们使用了最暴力的方法(唔,看起来还不错)

但当我们真正提交上去却只有大大的

Wrong Answer !

 why?

2.2简单来说,就是f[j]需要用到前面计算的数据已经被无意地篡改了。

在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维

后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用

的是第i轮的状态

 例如:

一维状态,第i轮对体积为 3 的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。这样计算的话,w[i]就会多次使用,但实际上只需使用一次。

我们就换一个思路:如果我们是逆序(j=m;i>=0;j++),则f[较大体积]更新到f[较小体积]就不会发生篡改的情况了

上代码:

​
​
#include <iostream>
using namespace std;
const int N=1005;
int f[N],v[N],w[N];
int main()
{
    int m,n,i,j;
   scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
    scanf("%d%d",&v[i],&w[i]);
    for(i=1;i<=n;i++)
    for(j=m;j>=1;j--)
        if(j<v[i])
        f[j]=f[j];
        else
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    printf("%d",f[m]);
}

​

​

但,f[i]=f[i]是个冗余的代码,索性直接把if条件摆在for循环中

#include <iostream>
using namespace std;
const int N=1005;
int f[N],v[N],w[N];
int main()
{
    int m,n,i,j;
   scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
    scanf("%d%d",&v[i],&w[i]);
    for(i=1;i<=n;i++)
    for(j = m; j >= v[i]; j--)  
    f[j] = max(f[j], f[j - v[i]] + w[i]);
    printf("%d",f[m]);
}

​


2.3 优化输入

我们注意到在处理数据时,我们是一个物品一个物品,一个一个体积的枚举

因此我们可以不必开两个数组记录体积和价值,而是边输入边处理

直接去除下边for(i)循环

#include <iostream>
using namespace std;
const int N=1005;
int f[N],v[N],w[N];
int main()
{
    int m,n,i,j;
   scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
   {
    scanf("%d%d",&v[i],&w[i]);
    for(j = m; j >= v[i]; j--)  
    f[j] = max(f[j], f[j - v[i]] + w[i]);
   }
    printf("%d",f[m]);
}

总结

以上就是今天要讲的内容,本文仅仅简单分析了01背包的算法,作者水平有限,望各位读者海涵与指正。

ps:该题来自Acming

2. 01背包问题 - AcWing题库

本文含有隐藏内容,请 开通VIP 后查看