01背包问题 二维 一维优化

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

第 i 件物品的体积是 vi,价值是 wi。

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

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

推导分析:

v 为体积 w为价值         f[ i ] [ j ] 表示在前 i 个物品里选择的总体积小于等于 j 时的最大价值

将物品分为两个集合 : S1( 含有第 i 个物品 ) S2( 不含有第 i 个物品 )

S1 的表达式为 f [ i - 1 ] [ j ]         S2的表达式为 f [ i -1 ] [ j - v [ i ] ] + w [ i ] 

那么f [ i ] [ j ] = max (  f [ i - 1 ] [ j ]  ,  f [ i -1 ] [ j - v [ i ] ] + w [ i ]  ) 

弄明白关系后这个式子就是动态规划的状态转移方程 以上是二维方程

做出二维之后就可以考虑优化为一维

为什么可以转为一维

首先观察状态转移方程 dp[i][j]是由 dp[i-1][jxxxx]推导而来,仅看第一个维度,即i - 1 与 i ,可以发现第i层是由上一层推导而来的。

故我们不必要保存i - 2 层,比如我们计算第三层是只需要第二层的。不需要第一层的数据。

当我们去掉i时,即我们不需要控制第几层,只需要长度为j的数组,保存确认过最新的一层。作为下一层的参考。例如我们计算第三层dp时,此时dp原数据保存的是第二层的结果。

为什么要逆序

首先,通过上一个问题,我们确认了我们目前一维的dp数组,保存的是确认过的最新一层的数据,即上一层的数据。

当我们计算当前层时,对于二维时的状态转移方程有

dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);

可以看到,dp[i - 1][j - v[i]] + w[i] 使用的上一层的原始数据(dp[i - 1]),而我们使用一维的状态转移方程时有

dp[j] = max(dp[j], dp[j - v[i]] + w[i]);

当我们从小到大更新是, 因为j - v[i] 是严格小于j 的,所以我们可以举个例子 dp[3] = max(dp[3], dp[2] + 1); 因为我们是从小到大更新的,所以当更新到dp[3]的时候,dp[2]已经更新过了,已经不是上一层的dp[2]。

而当我们逆序更新时有,举例 dp[8] = max(dp[8], dp[6] + 2)当更新dp[8]时,dp[6]还没有被更新,还是上一层的数据,这样才能保证没有读入脏数据。

作者:断然
链接:https://www.acwing.com/solution/content/116859/

//二维背包
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e3+10;;
int n , m ;
int v[N] , w[N] , f[N][N] ;

int main()
{
    cin >> n >> m ;
    for(int i = 1 ; i <= n ; i++){
        cin >> v[i] >> w[i] ;
    }
    
    for(int i = 1 ; i <= n ; i ++ ){
        for(int j = 0 ; j <= m ; j ++ ){
            f[i][j] = f[i-1][j] ;
            if( j >= v[i] ) f[i][j] = max( f[i][j] , f[i-1][j - v[i] ] + w[i] );
        }
    }
    cout << f[n][m] << endl;
    return 0;
}
//一维优化
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e3+10;;
int n , m ;
int v[N] , w[N] , f[N] ;

int main()
{
    cin >> n >> m ;
    for(int i = 1 ; i <= n ; i++){
        cin >> v[i] >> w[i] ;
    }
    
    for(int i = 1 ; i <= n ; i ++ ){
        for(int j = m ; j >= v[i] ; j -- ){ //改为从大到小
            f[j] = max( f[j] , f[j - v[i] ] + w[i] );
        }
    }
    cout << f[m] << endl;
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值