动态规划 - 背包问题

01背包

  • 01背包状态表示:
    • dp[i][j] 表示前 i 组中选,体积不超过 j 的所有选法中的最大价值
  • i 位置状态划分:
    • 不选第 i 个:dp[i - 1][j]
    • 选第 i 个:dp[i - 1][j - v[i]] + w[i] ( 条件:体积 j >= v[i])
  • 优化:用到的是 i - 1 即上一组的状态更新,所有需反向循环 j 来更新滚动数组
  • 优化版:
const int M = 1010;
int N, V, v[M], w[M], dp[M];
int main()
{
    cin >> N >> V;
    for(int i = 1; i <= N; ++i)
        cin >> v[i] >> w[i];
    // 滚动数组优化
    for(int i = 1; i <= N; ++i)
        for(int j = V; j >= v[i]; --j)
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
    cout << dp[V];
    return 0;
}

完全背包

  • 完全背包状态表示:
    • dp[i][j] 表示前 i 组中选,体积不超过 j 的所有选法中的最大价值
  • i 位置状态划分:
    • 第 i 个选 0 个:dp[i - 1][j]
    • 第 i 个选 k 个:dp[i - 1][j - k * v[i]] + k * w[i] ( 条件:体积 j >= k * v[i])
  • 推导:
    • dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i], dp[i - 1][j - 2 * v[i]] + 2 * w[i], ...)
    • dp[i][j - v[i]] = max(dp[i - 1][j - v[i]], dp[i - 1][j - 2 * v[i]] + w[i], dp[i - 1][j - 3 * v[i]] + 2 * w[i], ...)
    • dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]); (当 j >= v[i]时)
  • 优化:用到的是 i 即当前组的已更新的状态更新,所有正向循环 j 来更新滚动数组
  • 未优化+优化版:
const int M = 1010;
int N, V, v[M], w[M], dp[M];
int main()
{
    cin >> N >> V;
    for(int i = 1; i <= N; ++i)
        cin >> v[i] >> w[i];
        
    // 未优化
    for(int i = 1; i <= N; ++i)
    {
        for(int j = 0; j <= V; ++j)
        {
            dp[i][j] = dp[i - 1][j];
            if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
        }
    }
    
    // 滚动数组优化
    for(int i = 1; i <= N; ++i)
        for(int j = v[i]; j <= V; ++j)
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
    
    cout << dp[V];
    return 0;
}

多重背包

  • 分析过程与完全背包类似,但是数量有限制
  • 未优化版:按照第 i 个物品个数枚举
const int M = 1010;
int N, V, v[M], w[M], s[M], dp[M][M];
int main()
{
    cin >> N >> V;
    for(int i = 1; i <= N; ++i)
        cin >> v[i] >> w[i] >> s[i];
        
    for(int i = 1; i <= N; ++i)
    {
        for(int j = 0; j <= V; ++j)
        {
            for(int k = 0; k <= s[i] && k * v[i] <= j; ++k)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);            
        }
    }
            
    cout << dp[N][V];
    return 0;
}

分组背包问题

  • 分组背包状态表示:
    • dp[i][j] 表示前 i 组中选,体积不超过 j 的所有选法中的最大价值
  • i 位置状态划分:
    • 不选第 i 组:dp[i - 1][j]
    • 选 i 组第k个:dp[i - 1][j - v[i][k]] + w[i][k] ( 条件:体积 j >= v[i][k])
  • 优化:用到的是 i - 1 即上一组的状态更新,所有需反向循环 j 来更新滚动数组
  • 未优化版:
#include <iostream>

using namespace std;

const int M = 110;
int N, V, s[M], v[M][M], w[M][M];

int dp[M][M];

int main()
{
    cin >> N >> V;
    for(int i = 1; i <= N; ++i)
    {
        cin >> s[i];
        for(int j = 0; j < s[i]; ++j)
            cin >> v[i][j] >> w[i][j];
    }
    
    for(int i = 1; i <= N; ++i)
    {
        for(int j = 0; j <= V; ++j)
        {
            dp[i][j] = dp[i - 1][j];
            
         // for(int k = 0; k < s[i] && j >= v[i][k]; ++k) 
         // 错误,循环条件不满足会直接跳出,不能循环到每一个k
            for(int k = 0; k < s[i]; ++k)
            {
                if(j >= v[i][k])
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i][k]] + w[i][k]);
            }
        }
    }
    cout << dp[N][V];
    
    return 0;
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值