题解:洛谷 P17012 条形蛋糕

【题目来源】

洛谷:P17012 [GESP202606 六级] 条形蛋糕 - 洛谷

【题目描述】

寒假到了,小杨同学打算找一份兼职,顺便体验一下打工人的生活。

小杨同学给一家蛋糕店发送了一份自己的简历,希望可以在寒假来这里帮忙。店长最近正好遇到了一个难题:店里每天会做一条长条蛋糕,但是不同长度的蛋糕块卖出的价格不同,应该怎么分才能卖得最多呢?

有趣的是店长曾经学习过计算机专业。他最近对动态规划算法很感兴趣,于是打算用这个问题考一考小杨同学,问题如下:

  • 给定一条长度为 \(n\) 的长条蛋糕和一个价格表,该价格表表示长度为 \(i\) (\(i = 1, 2, \dots, n\)) 的蛋糕块的价格为 \(p_i\)。求蛋糕的分割方案,使得总销售价格最大,注意蛋糕块的长度必须为整数。

【输入】

第一行一个正整数 \(n\) (\(1 \le n \le 10^3\)),表示长条蛋糕的总长度。

第二行 \(n\) 个正整数 \(p_1, p_2, \dots, p_n\) (\(1 \le p_i \le 10^5\)),表示不同长度蛋糕块的价格。

【输出】

一行一个正整数,表示最大总销售价格。

【输入样例】

4
1 5 8 9

【输出样例】

10

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的蛋糕和价格表 \(p_1, p_2, \ldots, p_n\),要求将蛋糕分割为若干整数长度的块,使得总价格最大。这是一个完全背包问题,核心在于每种长度的蛋糕块可以无限选取,在总长度限制下最大化总价值。

  2. 算法选择

    • 动态规划(完全背包):设 \(dp[j]\) 表示总长度为 \(j\) 时的最大价格,每种长度 \(i\) 的物品可以无限次选取
    • 状态转移\(dp[j] = \max(dp[j], dp[j-i] + p_i)\),表示在总长度 \(j\) 中选择一块长度为 \(i\) 的蛋糕
  3. 关键步骤

    • 初始化\(dp[0] = 0\),其余 \(dp[j] = 0\)(正整数价格保证非负,初始化为 \(0\) 即可)
    • 外层枚举物品\(i\)\(1\)\(n\)):遍历每种可选的蛋糕长度
    • 内层枚举容量\(j\)\(i\)\(n\)):正序遍历,允许同一物品重复选取
    • 状态转移\(dp[j] \leftarrow \max(dp[j], dp[j-i] + p_i)\)
    • 输出结果\(dp[n]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^2)\),外层 \(n\) 种长度,内层最多 \(n\) 的容量
    • 空间复杂度:\(O(n)\),一维 \(dp\) 数组即可,二维数组可优化为一维
  5. 完全背包的核心思想

    • 无限选取特性:与 \(0/1\) 背包不同,完全背包中每种物品可以选取任意多次,因此内层循环正序遍历(\(j\)\(i\)\(n\)),使得 \(dp[j-i]\) 可能已经包含了当前物品 \(i\),实现无限选取
    • 最优子结构:总长度为 \(j\) 的最优解可以由总长度为 \(j-i\) 的最优解加上一块长度为 \(i\) 的蛋糕得到,即 \(dp[j] = \max_{1 \leq i \leq j}\{dp[j-i] + p_i\}\)
    • 空间优化:一维数组即可满足需求,\(dp[j]\) 在更新时引用的是同一轮已更新的 \(dp[j-i]\),隐含了"无限选取"的语义
    • \(0/1\) 背包的区别\(0/1\) 背包内层逆序遍历防止重复选取,完全背包正序遍历允许重复选取
    • 适用于物品无限供应、在容量限制下最大化价值的资源分配类问题

【算法标签】

普及- #完全背包

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 1005;                // 常量:最大蛋糕长度

int n;                             // n: 蛋糕总长度
int p[N];                          // p[i]: 长度为 i 的蛋糕块的价格
int dp[N][N];                      // dp[i][j]: 考虑前 i 种长度,总长度为 j 时的最大价格

int main()
{
    cin >> n;                      // 读入蛋糕总长度

    for (int i = 1; i <= n; i++)  // 读入各长度蛋糕块的价格
        cin >> p[i];

    for (int i = 1; i <= n; i++)  // 枚举考虑的蛋糕长度种类(1到i)
        for (int j = 0; j <= n; j++)  // 枚举当前总长度
        {
            dp[i][j] = dp[i - 1][j];  // 不选长度为 i 的蛋糕块

            if (j >= i)                // 如果当前总长度可以放下长度为 i 的蛋糕块
                dp[i][j] = max(dp[i][j], dp[i][j - i] + p[i]);  // 选一块长度为 i 的蛋糕
        }

    cout << dp[n][n] << endl;      // 输出最大总销售价格

    return 0;
}

【运行结果】

4
1 5 8 9
10

【算法真传】

从一道 GESP 真题出发:聊聊完全背包与无限分割的最优策略

posted @ 2026-07-07 11:23  团爸讲算法  阅读(48)  评论(0)    收藏  举报