题解:洛谷 P2678 跳石头
【题目来源】
洛谷:P2678 [NOIP 2015 提高组] 跳石头 - 洛谷(luogu.com.cn)
【题目描述】
一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 \(N\) 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 块岩石(不能移走起点和终点的岩石)。
【输入】
第一行包含三个整数 \(L,N,M\),分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 \(L\ge 1\) 且 \(N\ge M\ge 0\)。
接下来 \(N\) 行,每行一个整数,第 \(i\) 行的整数 \(D_i(0\lt D_i\lt L)\),表示第 \(i\) 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
【输出】
一个整数,即最短跳跃距离的最大值。
【输入样例】
25 5 2
2
11
14
17
21
【输出样例】
4
【核心思想】
-
问题分析:给定河道长度 \(L\),起点终点间有 \(N\) 块岩石,最多移走 \(M\) 块(不含起点终点)。求移走岩石后,最短跳跃距离的最大值。这是一个整数二分 + 贪心验证问题,与路标设置(P3853)类似但目标相反:本题是"最大化最小值",P3853是"最小化最大值"。
-
算法选择:
- 整数二分:在 \([1, L]\) 范围内二分查找最大可行的最短跳跃距离
- 贪心验证:对于给定的最短距离 \(mid\),贪心移走岩石:若当前岩石到上一个保留岩石的距离 \(< mid\),则移走当前岩石;否则保留。统计移走数量是否 \(\leq M\)
-
关键步骤:
- 读入数据:\(L, N, M\) 和 \(N\) 个岩石位置,设 \(a[N+1] = L\)(终点)
- 二分查找(\(l = 1, r = L\) 或 \(10^9\)):
mid = (l + r + 1) / 2(向上取整,避免死循环)check(mid):cnt = 0, last = 0(上一个保留的岩石索引,起点为0)- 遍历 \(i\) 从 \(1\) 到 \(N+1\):
- 若
a[i] - a[last] < mid:cnt++(移走当前岩石) - 否则:
last = i(保留)
- 若
- 返回
cnt <= M
- 若
check(mid)为真:l = mid(尝试更大) - 否则:
r = mid - 1
- 输出
l
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log L)\),二分 \(O(\log L)\),每次验证 \(O(N)\)
- 空间复杂度:\(O(N)\),存储岩石位置
-
整数二分最大化最小值的核心思想:
- 答案单调性:若最短距离 \(mid\) 可行(移走岩石 \(\leq M\)),则所有 \(< mid\) 的值也可行;若不可行,则所有 \(> mid\) 的值也不可行。单调性支撑二分查找最大值
- 贪心保留策略:验证时,遇到距离不足的岩石就移走,这是最优的——因为移走当前岩石可以"合并"间距,使后续跳跃距离更大,而保留当前岩石会限制后续选择
- 向上取整防死循环:
mid = (l + r + 1) / 2确保当l + 1 == r时取 \(r\),配合l = mid的更新方式,避免死循环 - 与路标设置的对比:本题是"移走岩石使间距变大",P3853是"增设路标使间距变小",但都是二分答案 + 贪心验证的框架
- 适用于"最大化最小间隔"类问题,核心在于二分答案和贪心验证
【解题思路】

【算法标签】
普及 #整数二分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 全局变量定义
int length, n, m, a[50005], last; // length: 道路总长度, n: 已有石头数, m: 可移除石头数
// a[]: 存储相邻石头间距, last: 记录上一个石头位置
int main()
{
// 输入道路总长度、已有石头数和可移除石头数
cin >> length >> n >> m;
// 初始化上一个石头位置为0(起点)
last = 0;
// 输入每个石头的位置并计算相邻石头间距
for (int i = 0; i < n; i++) {
int now; // 当前石头位置
cin >> now;
a[i] = now - last; // 计算当前石头与前一个石头的间距
last = now; // 更新上一个石头位置
}
// 计算最后一个石头到终点的间距
a[n] = length - last;
// 特殊情况处理:如果可移除石头数等于已有石头数
if (n == m) {
cout << length << endl; // 直接输出道路总长度
return 0;
}
// 二分查找初始化
int l = 1, r = length / (n - m); // l: 最小可能间距, r: 最大可能间距
// 二分查找过程
while (l <= r) {
int tmp, k = 0; // tmp: 临时累计间距, k: 需要移除的石头数
int mid = l + (r - l) / 2; // 中间值(当前猜测的最小间距)
// 检查当前mid是否可行
for (int i = 0; i <= n; i++) {
tmp = a[i]; // 初始化为当前间距
// 如果当前间距小于mid,则合并下一段间距
while (tmp < mid && i <= n) {
i++;
tmp += a[i]; // 合并间距
k++; // 增加需要移除的石头数
}
}
// 根据检查结果调整二分范围
if (k <= m) l = mid + 1; // 移除石头数不足,可以尝试更大的间距
else r = mid - 1; // 移除石头数过多,需要减小间距
}
// 输出最终结果(最大可能的最小间距)
cout << r << endl;
return 0;
}
// 使用老师的板子再写一遍
#include <bits/stdc++.h>
using namespace std;
int L, n, m, a[50005], ans;
// 检查函数:判断是否可以在移除最多m块石头的情况下
// 使得所有相邻石头间的最短距离不小于x
bool check(int x)
{
int cnt = 0; // 记录需要移除的石头数量
int last = 0; // 上一个未被移除的石头位置
for (int i = 1; i <= n + 1; i++)
{
// 如果当前石头到上一个未移除石头的距离小于x
if (a[i] - a[last] < x)
{
cnt++; // 移除当前石头
}
else
{
last = i; // 保留当前石头
}
}
return cnt <= m; // 判断需要移除的石头是否不超过m块
}
// 二分查找函数:寻找最大的最小距离x
int find()
{
int l = 0, r = 1000000000 + 1; // 搜索范围
while (l + 1 < r) // 开区间二分
{
int mid = (l + r) / 2;
if (check(mid)) // 如果mid可行
{
l = mid; // 尝试更大的值
}
else
{
r = mid; // 尝试更小的值
}
}
return l; // 返回最大的可行值
}
int main()
{
cin >> L >> n >> m; // 输入峡谷宽度L,石头数量n,可移除石头数量m
for (int i = 1; i <= n; i++)
{
cin >> a[i]; // 输入每个石头的位置
}
a[n + 1] = L; // 最后一个位置是峡谷终点
ans = find(); // 查找答案
cout << ans << endl;
return 0;
}
// 使用acwing模板二刷
#include <bits/stdc++.h>
using namespace std;
const int N = 50005;
int L, n, m, a[N], ans;
// 检查函数:判断是否可以在移除最多m块石头的情况下,
// 使得所有相邻石头间的最短距离不小于x
bool check(int x)
{
int cnt = 0; // 记录需要移除的石头数量
int last = 0; // 上一个未移除的石头位置(起点是0)
for (int i = 1; i <= n + 1; i++) // 遍历所有石头+终点
{
// 如果当前石头到上一个未移除石头的距离小于x
if (a[i] - a[last] < x)
{
cnt++; // 移除当前石头
}
else
{
last = i; // 保留当前石头
}
}
return cnt <= m; // 判断需要移除的石头是否不超过m块
}
// 二分查找:寻找最大的最小距离x
int find()
{
int l = 1, r = 1000000000; // 搜索范围[1, 1e9]
while (l < r) // 二分查找
{
int mid = l + r + 1 >> 1; // 向上取整
if (check(mid)) // 如果mid可行
{
l = mid; // 尝试更大的值
}
else
{
r = mid - 1; // 尝试更小的值
}
}
return l; // 返回最大的可行值
}
int main()
{
cin >> L >> n >> m; // 峡谷长度L,石头数量n,最多移除m块
for (int i = 1; i <= n; i++)
{
cin >> a[i]; // 输入石头位置
}
a[n + 1] = L; // 终点位置
ans = find(); // 计算答案
cout << ans << endl;
return 0;
}
【运行结果】
25 5 2
2
11
14
17
21
4
浙公网安备 33010602011771号