问题描述
一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。
组委会已经选择好了两块岩石作为比赛起点和终点。
在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。
在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。
由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
输入格式
输入文件第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。
接下来 N 行,每行一个整数,第 i 行的整数 Di(0 < Di < L) 表示第 i 块岩石与起点的距离。
这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式
输出文件只包含一个整数,即最短跳跃距离的最大值。
数据范围
0≤M≤N≤500000
1≤L≤109
输入样例:
25 5 2 2 11 14 17 21
输出样例:
4
思路
这道题是二分套路题中的经典问题 —— 最小值最大化,除此之外还有最大值最小化问题,解法和这个差不太多。
我们先来考虑暴力法如何做,需要枚举 n 块石头中选取 m 个石头的所有组合,时间复杂度直接爆炸!
所以需要优雅一点的暴力,同样是枚举,不过我们枚举的是最短距离的最大值 d。对这个临界值进行二分,每次二分都去判断当前最短距离的最大值是否能通过移走 m 个以内的石头达到中位值 mid,如果满足条件,则使 left=mid,反之使 right=mid-1,因为我们要使最小值尽可能的大,所以当 mid 已经满足条件时我们任然要继续往更大的值进行尝试,另外 mid 如果此时已经满足条件则有可能就是最终答案,所以需要用 left 保存下来。
另外,我们 check 中可以采用贪心思想:
如果当前石头和上一块石头的距离小于 len,则将当前石头拿走。这是因为如果某个最优解中是拿走了上一块石头,那么我们可以改成留下上一块石头,拿走当前这块石头,这样总共拿走的石头数量不变,所以当前选法也是一组最优解。
如果当前石头和上一块石头的距离大于等于 len,则将上一块石头更新成当前这块。
扫描结束后观察拿走的石头数量是否小于等于 m,如果小于等于则满足条件。
代码
#include<cstdio> int len, n, m; int stone[50005]; bool check(int d) { //检查距离d是否合适 int num = 0; //num记录搬走石头的数量 int pos = 0; //当前站立的石头 for (int i = 1; i <= n; ++i) if (stone[i] - pos < d) num++; //第i块石头可以搬走 else pos = stone[i]; //第i块石头不能搬走 if (num <= m) return true; //要移动的石头比m少,满足条件 else return false; //要移动的石头比m多,不满足条件 } int main() { scanf("%d%d%d", &len, &n, &m); for (int i = 1; i <= n; ++i) scanf("%d", &stone[i]); stone[++n] = len; int L = 0, R = len, mid; while (L < R) { mid = L + (R - L + 1) / 2; if (check(mid)) L = mid; //满足条件 else R = mid - 1; //不满足条件,说明mid大了,调小一点 } printf("%d\n", L); return 0; }