题解:洛谷 P9123 Watching Mooloo

【题目来源】

洛谷:P9123 [USACO23FEB] Watching Mooloo B - 洛谷

【题目描述】

贝茜喜欢看 Mooloo 的演出。因为她是一只忙碌的奶牛,她计划在接下来的 \(N (1 \le N \le 10^5)\) 天去看演出。因为 Mooloo 提供了订阅服务,她想要使她花费的钱最少。

Mooloo 有一个有趣的订阅服务系统:若要在此之后的连续 \(d\) 天看演出,则在订阅时需要花费 \(d+K(1 \le K \le 10^9)\) 个单位价格。你可以随时订阅;若本次订阅已经过期,你可以根据需要订阅多次。基于以上条件,请计算出贝茜最少要花费多少个单位价格,才能完成她的计划。

【输入】

第一行输入两个正整数 \(N\)\(K\)

第二行输入 \(N\) 个正整数,表示在这些天里,贝茜会看 Mooloo 的演出:\(1 \le d_1<d_2<\cdots<d_N \le 10^{14}\)

【输出】

请注意,此问题中可能需要使用 64 位整数数据类型(如 C 或 C++ 中的 long long)。

【输入样例】

2 4
7 9

【输出样例】

7

【核心思想】

  1. 问题分析:Bessie 有 \(N\) 个需要看演出的日期 \(d_i\)(严格递增)。订阅规则是:若订阅覆盖连续 \(d\) 天(包含中间不看的日子),花费为 \(d + K\)。订阅可多次购买,每次从任意天开始。求覆盖所有指定日期的最小总花费。本质上这是一个一维数轴上的区间覆盖优化问题,每个订阅可看作一个区间,花费为区间长度加固定费用 \(K\),目标是覆盖所有给定点。

  2. 算法选择

    • 贪心 + 动态规划 / 递推:关键观察是决策具有无后效性——对于相邻的两个观看日 \(d_{i-1}\)\(d_i\),要么为 \(d_i\) 单独开始一次新订阅(花费 \(K+1\)),要么将 \(d_i\) 合并到覆盖 \(d_{i-1}\) 的订阅中(额外花费 \(d_i - d_{i-1}\),因为订阅结束日期延长了这么多天)。每次选择两者中较小的代价即可得到全局最优。
    • 线性扫描:只需按日期顺序处理,用累加变量 \(ans\) 记录最小总花费。
  3. 关键步骤(基于学而思课堂笔记的贪心实现):

    • 读入数据:读取 \(N, K\)\(N\) 个观看日期 \(d[1..N]\)
    • 初始化:第一个观看日必须开启一次订阅,花费 \(ans = K + 1\)(订阅覆盖 \(d_1\) 当天,即连续1天)。
    • 迭代决策:对于 \(i = 2\)\(N\)
      • 计算将第 \(i\) 个观看日纳入上一次订阅所需的额外费用:\(cost\_extend = d_i - d_{i-1}\)(订阅结束日从 \(d_{i-1}\) 延长到 \(d_i\))。
      • 计算为第 \(i\) 个观看日重新开启一次新订阅的费用:\(cost\_new = K + 1\)
      • 取较小者累加到答案:\(ans += \min(cost\_extend, cost\_new)\)
    • 输出答案\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),仅需一次线性扫描。
    • 空间复杂度:\(O(N)\)\(O(1)\)(若流式读取则只需存储前一个日期)。
  5. 贪心 + 线性递推的核心思想

    • 相邻决策独立性:由于订阅之间是连续的,且每个观看日必须被覆盖,最优策略中每个订阅覆盖的必定是日期序列中的一段。因此,决策只需在相邻观看日之间进行:是“延续”还是“断开”订阅。
    • 代价比较:延续订阅的边际成本是时间间隔 \(d_i - d_{i-1}\),而断开后重新订阅的固定成本是 \(K+1\)。选择更小者即为局部最优,由于没有其他约束,局部最优累加即为全局最优。
    • 对“连续天数”的理解:订阅覆盖的连续天数是从订阅开始日到结束日的总天数,即使中间没有演出也需要付费。因此延长订阅的花费正好是两天之间的日期差。
    • 适用场景:适用于在有序时间点上进行“覆盖区间”且区间成本为“长度+常数”的优化问题,核心是相邻点间“延续 vs 重启”的贪心选择。

【解题思路】

【算法标签】

普及- #贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, k;
long long a[100005], dp[100005], sum=0;
int main()
{
    cin >> n >> k;  // 输入n和k
    for (int i=1; i<=n; i++) {  // 输入哪些天要看演出
        cin >> a[i];
    }
    dp[1] = 1+k;  // 第一天为1+k
    for (int i=2; i<=n; i++) {  // 遍历后面n-1天
        dp[i] = min(dp[i-1]+1+k, dp[i-1]+a[i]-a[i-1]);  // dp递推式,取1+k和a[i]-a[i-1]的最小值
    }
    cout << dp[n] << endl;  // 输出最后一天的价格,就是总计最小值
    return 0;
}
// 学而思课堂笔记
#include <bits/stdc++.h>
using namespace std;
int n, k;
long long ans, d[100005];
int main()
{
    cin >> n >> k;
    for (int i=1; i<=n; i++) {
        cin >> d[i];
        if (i==1) ans = k+1;  // 如果是开始订阅的第一天
        else {  // 如果不是开始订阅的第一天
            long long x = d[i] - d[i-1];  // 续约的订阅花费
            long long y = k+1;  // 重新订阅花费
            ans += min(x, y);
        }
    }
    cout << ans << endl;
    return 0;
}

【运行结果】

2 4
7 9
7
posted @ 2026-08-29 09:41  团爸讲算法  阅读(10)  评论(0)    收藏  举报