题解:洛谷 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
【核心思想】
-
问题分析:Bessie 有 \(N\) 个需要看演出的日期 \(d_i\)(严格递增)。订阅规则是:若订阅覆盖连续 \(d\) 天(包含中间不看的日子),花费为 \(d + K\)。订阅可多次购买,每次从任意天开始。求覆盖所有指定日期的最小总花费。本质上这是一个一维数轴上的区间覆盖优化问题,每个订阅可看作一个区间,花费为区间长度加固定费用 \(K\),目标是覆盖所有给定点。
-
算法选择:
- 贪心 + 动态规划 / 递推:关键观察是决策具有无后效性——对于相邻的两个观看日 \(d_{i-1}\) 和 \(d_i\),要么为 \(d_i\) 单独开始一次新订阅(花费 \(K+1\)),要么将 \(d_i\) 合并到覆盖 \(d_{i-1}\) 的订阅中(额外花费 \(d_i - d_{i-1}\),因为订阅结束日期延长了这么多天)。每次选择两者中较小的代价即可得到全局最优。
- 线性扫描:只需按日期顺序处理,用累加变量 \(ans\) 记录最小总花费。
-
关键步骤(基于学而思课堂笔记的贪心实现):
- 读入数据:读取 \(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\)。
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),仅需一次线性扫描。
- 空间复杂度:\(O(N)\) 或 \(O(1)\)(若流式读取则只需存储前一个日期)。
-
贪心 + 线性递推的核心思想:
- 相邻决策独立性:由于订阅之间是连续的,且每个观看日必须被覆盖,最优策略中每个订阅覆盖的必定是日期序列中的一段。因此,决策只需在相邻观看日之间进行:是“延续”还是“断开”订阅。
- 代价比较:延续订阅的边际成本是时间间隔 \(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
浙公网安备 33010602011771号