D. Serval and Kaitenzushi Buffet

Serval and Kaitenzushi Buffet 题解:优先队列 + 数学推导

题目链接

题目分析

Serval 在一家回转寿司餐厅用餐,他需要在 $ n $ 分钟内最大化所吃寿司的美味值总和。每盘寿司有 $ k $ 块,第 $ i $ 盘寿司的美味值为 $ a_i $。在每一分钟,Serval 可以选择以下操作之一:

  1. 取走当前盘子,增加未吃完的寿司块数 $ r $(即 $ r += k $)。
  2. 吃掉一块寿司,减少未吃完的寿司块数 $ r $(即 $ r -= 1 $)。
  3. 什么都不做,保持 $ r $ 不变。

最终,$ r $ 必须为 0(即所有寿司都必须吃完)。我们需要帮助 Serval 最大化他取走的所有盘子的美味值总和。

听起来有点复杂?别急,我们慢慢来~


思路大意

这道题的核心目标是找到一种策略,使得 Serval 能够在 $ n $ 分钟内尽可能多地取走高美味值的盘子,并保证所有寿司都能被吃完。

核心观察

为了最大化美味值总和,我们需要尽可能多地取走高美味值的盘子,并保证在 $ n $ 分钟内可以吃完所有寿司。

数学推导

假设我们选择了 $ m $ 盘寿司,则总共需要吃 $ m \times k $ 块寿司。由于每分钟最多只能吃 1 块寿司,因此完成这些寿司需要至少 $ m \times k $ 分钟。同时,我们还有 $ n - m $ 分钟用于取盘子或什么都不做。

因此,满足条件的 $ m $ 必须满足以下不等式:

\[m \times (k+1) \leq n \]

即:

\[m \leq \left\lfloor \frac{n}{k+1} \right\rfloor \]

这意味着我们可以最多取走 $ \left\lfloor \frac{n}{k+1} \right\rfloor $ 盘寿司。

接下来的问题是如何选择这 $ m $ 盘寿司以使美味值最大。显然,我们应该优先选择美味值最高的盘子。


算法设计

基于上述分析,我们可以设计如下算法:

  1. 使用一个优先队列priority_queue<int>)来维护当前遇到的盘子的美味值。
  2. 遍历所有盘子,将每个盘子的美味值加入优先队列。
  3. 当遍历到某个时刻时,判断是否应该从优先队列中取出一个盘子计入答案。具体地,当剩余时间不足以处理更多的盘子时,我们就应该从优先队列中取出一个盘子。

代码详解

以下是完整的代码实现及其详细解释:

#include <bits/stdc++.h>
#define int long long
#define all(x) x.begin(),x.end()
#define rall(x) x.rbegin(),x.rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int mod = 998244353;

void solve() {
    int n, k; cin >> n >> k; // 输入 n 和 k
    vector<int> a(n + 1); // 存储每盘寿司的美味值
    for (int i = 1; i <= n; i++) cin >> a[i]; // 输入美味值

    priority_queue<int> pq; // 优先队列,存储当前遇到的盘子的美味值
    int ans = 0; // 答案初始化为 0

    for (int i = 1; i <= n; i++) {
        pq.push(a[i]); // 将当前盘子的美味值加入优先队列

        // 判断是否需要从优先队列中取出一个盘子
        if ((n - i) % (k + 1) == k) {
            ans += pq.top(); // 取出当前美味值最大的盘子
            pq.pop(); // 从优先队列中移除该盘子
        }
    }

    cout << ans << endl; // 输出答案
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); // 加速输入输出
    int _ = 1; cin >> _; // 测试用例数量
    while (_--) solve(); // 处理每个测试用例
}

代码关键部分解析

priority_queue<int> pq;

  • 作用:优先队列是一种数据结构,能够高效地维护一组元素,并支持快速插入和删除最大值的操作。
  • 特性:在本题中,我们使用优先队列来存储当前遇到的所有盘子的美味值,并始终保持美味值最大的盘子在队列顶部。
  • 时间复杂度:插入和删除操作的时间复杂度均为 $ O(\log m) $,其中 $ m $ 是队列中的元素数量。

关键逻辑

for (int i = 1; i <= n; i++) {
    pq.push(a[i]);
    if ((n - i) % (k + 1) == k) {
        ans += pq.top();
        pq.pop();
    }
}

逐行解释

  1. pq.push(a[i]);

    • 将当前盘子的美味值 $ a[i] $ 加入优先队列。
    • 此时,优先队列会自动调整内部结构,确保队列顶部始终是当前美味值最大的盘子。
  2. if ((n - i) % (k + 1) == k)

    • 这是判断是否应该从优先队列中取出一个盘子的关键条件。
    • 条件的核心思想是:在某些特定时刻,剩余时间不足以处理更多的盘子,因此我们需要从优先队列中取出一个盘子计入答案。
    • 具体地,当剩余时间 $ (n - i) $ 满足 $ (n - i) \mod (k + 1) == k $ 时,表示我们已经无法再取新的盘子,而应该开始选择之前取过的盘子。
  3. ans += pq.top();pq.pop();

    • 当满足条件时,从优先队列中取出当前美味值最大的盘子,并将其加入答案。
    • 同时,将该盘子从优先队列中移除。

数学公式的进一步解释

条件 \((n - i) \% (k + 1) == k\)

这个条件的含义可以通过以下推导理解:

  • 设当前时间为 $ i $,则剩余时间为 $ n - i $。
  • 如果我们要在剩余时间内处理完所有已取的盘子,则需要满足:

    \[\text{已取盘子数} \times k \leq \text{剩余时间} \]

  • 换句话说,如果我们已经取了 $ m $ 盘寿司,则需要满足:

    \[m \times k \leq (n - i) \]

  • 当 $ (n - i) % (k + 1) == k $ 时,表示剩余时间刚好足够处理一个新的盘子,因此我们需要从优先队列中取出一个盘子。

示例分析

输入

5
5 2
3 6 4 1 2
7 1
3 1 4 1 5 9 2
4 3
4 3 2 1
6 2
1 3 5 2 4 6
6 1
1000000000 1 1000000000 1 1000000000 1

输出

6
16
4
6
3000000000

解释

  1. 第一个测试用例

    • $ n = 5, k = 2 $
    • 盘子美味值为 $ [3, 6, 4, 1, 2] $
    • 最多可以取 $ \left\lfloor \frac{5}{2} \right\rfloor = 2 $ 盘寿司。
    • 选择美味值为 $ 6 $ 的盘子,最终答案为 $ 6 $。
  2. 第二个测试用例

    • $ n = 7, k = 1 $
    • 盘子美味值为 $ [3, 1, 4, 1, 5, 9, 2] $
    • 最多可以取 $ \left\lfloor \frac{7}{1} \right\rfloor = 7 $ 盘寿司。
    • 选择美味值为 $ 3, 4, 9 $ 的盘子,最终答案为 $ 16 $。

posted @ 2025-03-24 18:22  archer2333  阅读(60)  评论(0)    收藏  举报