小F的超市购物策略 | 豆包MarsCode AI刷题

109 阅读5分钟

引言

动态规划(DP)是解决最优化问题的一种非常有效的方法。在编程竞赛和面试中,它经常用于解决一系列复杂的最大化、最小化问题。例如,在这道题目中,我们通过动态规划技术帮助小F最大化她在超市购物中的喜爱度,基于特定的购物规则。本文不仅解释了实现算法的思路,也分享了学习动态规划的一些经验和心得。

题目描述

小F正在超市购物,有 n 个商品排成一排,每个商品的价格为 ai,小F对它的喜爱度为 bi。所有商品的价格都是偶数。超市有一个活动:当小F以原价购买某件商品时,她可以用半价购买下一件右边相邻的商品(当然也可以选择以原价购买,这样下一件商品仍有机会半价购买)。然而,如果小F半价购买了一件商品,那么下一件相邻的商品只能原价购买。

给定 x 资金,求小F能获得的最大喜爱度总和。

示例

输入:

n = 4, x = 7, a = [2, 2, 6, 2], b = [3, 4, 5, 1]

输出:

12

解题思路

状态定义

我们用 f[i][j][k] 表示考虑前 i 个商品,当前资金为 j,状态 k(0 表示买了上一件商品,此商品半价,1 表示买得上一件商品,此商品原价,2 表示未买此商品)的情况下,能够获得的最大喜爱度。

转移方程

  • 状态 0:当前商品以半价购买

    if j - a[i] / 2 >= 0 and f[i - 1][1][j - a[i] / 2] > 0:
    f[i][0][j] = f[i - 1][1][j - a[i] / 2] + b[i]
    
  • 状态 1:当前商品以原价购买

    if j - a[i] >= 0:
    f[i][1][j] = max({f[i - 1][0][j - a[i]] + b[i], 
                      f[i - 1][1][j - a[i]] + b[i], f[i - 1][2][j - a[i]] + b[i]})
    
  • 状态 2:当前商品不购买

    f[i][2][j] = max({f[i - 1][2][j], f[i - 1][0][j], f[i - 1][1][j]})
    

初始化

如果 a[0] 价格不超过 x,则只有状态1有效,即购买第一个商品。

if (a[0] <= x) {
    f[0][1][a[0]] = b[0];
    ret = b[0];
}

最终最大值

在处理过程中,更新最大值:

ret = max({ret, f[i][0][j], f[i][1][j], f[i][2][j]});

代码实现

以下是完整代码:

#include <bits/stdc++.h>
using namespace std;

int solution(int n, int x, vector<int> a, vector<int> b) {
    int ret = 0;
    vector<vector<vector<int>>> f(n, vector<vector<int>>(3, vector<int>(x + 1, 0)));
    
    // 初始化第一个商品
    if (a[0] <= x) {
        f[0][1][a[0]] = b[0];
        ret = b[0];
    }
    
    // 状态转移
    for (int i = 1; i < n; i++) {
        for (int j = 0; j <= x; j++) {
            // 状态 0:当前商品以半价购买
            if (j - a[i] / 2 >= 0 && f[i - 1][1][j - a[i] / 2] > 0) {
                f[i][0][j] = f[i - 1][1][j - a[i] / 2] + b[i];
            }
            
            // 状态 1:当前商品以原价购买
            if (j - a[i] >= 0) {
                f[i][1][j] = max({f[i - 1][0][j - a[i]] + b[i],
                                  f[i - 1][1][j - a[i]] + b[i], f[i - 1][2][j - a[i]] + b[i]});
            }
            
            // 状态 2:当前商品不购买
            f[i][2][j] = max({f[i - 1][2][j], f[i - 1][0][j], f[i - 1][1][j]});
            
            // 更新最大喜爱度
            ret = max({ret, f[i][0][j], f[i][1][j], f[i][2][j]});
        }
    }
   
    return ret;
}

int main() {
    cout << (solution(4, 7, {2, 2, 6, 2}, {3, 4, 5, 1}) == 12) << endl;
    cout << (solution(3, 10, {4, 4, 4}, {2, 3, 5}) == 10) << endl;
    cout << (solution(5, 8, {2, 4, 4, 6, 2}, {1, 2, 3, 4, 5}) == 10) << endl;
    return 0;
}

知识总结与学习方法

动态规划的基本思想

动态规划的核心思想是将复杂问题分解为子问题,通过求解子问题最优解来构建原问题的最优解。其步骤可以简化为以下几步:

  1. 状态定义:确定用哪些变量来表示原问题和子问题的状态。
  2. 状态转移方程:给出如何根据子问题的解来构建原问题的解。
  3. 初始化:合理设置初始条件。
  4. 计算顺序:自底向上或者自顶向下的计算顺序。

刷题经验

  1. 找变量和状态:这是动态规划问题的基础。明确问题需要几个维度的状态来表示。
  2. 写转移方程:对于每个状态,考虑其如何由之前的状态转移过来,列出相应的转移方程。
  3. 边界条件:考虑特殊情况,设置初始值。
  4. 优化思路:动态规划表格的大小决定了算法的复杂度,尽量简化状态空间。

学习计划与总结

  1. 从易到难:从简单的一维 DP 开始,再逐步拓展到二维、三维。初期可结合经典问题,如斐波那契数列、背包问题等逐一练习。
  2. 动手实践:没有比实际写代码调试更好的学习方法了。准备一个刷题平台(如 leetcode 或豆包 MarsCode),每天挑战几个问题。
  3. 错题总结:将做错的题目收集整理,分析错误原因,总结正确的思路,以防再犯。
  4. 资源利用:合理使用学习资源,如刷题网站的解析、视频讲解,以及 AI 辅助工具(如豆包 MarsCode),进一步提高解题效率。

结合这些方法和计划来学习动态规划,将大大提升你解决最优化问题的能力。祝大家在动态规划学习中取得突破,迎接更高级别的挑战!