二分算法
1.P2678 跳石头
题目背景
一年一度的“跳石头”比赛又要开始了!
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 NNN 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 MMM 块岩石(不能移走起点和终点的岩石)。
输入格式
第一行包含三个整数 L,N,ML,N,ML,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L≥1L \geq 1L≥1 且 N≥M≥0N \geq M \geq 0N≥M≥0。
接下来 NNN 行,每行一个整数,第 iii 行的整数 Di(0<Di<L)D_i( 0 < D_i < L)Di(0<Di<L), 表示第 iii 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式
一个整数,即最短跳跃距离的最大值
输入输出样例
输入
25 5 2
2
11
14
17
21
输出
4
**题解:很显然从最短跳跃距离的最大值看出需二分答案,那就好办了,我们二分跳跃距离,把这个跳跃距离认为是最短跳跃距离,以这个距离为标准去移石头,使用一个check函数判断这个解是不是可行解。如果这个解是可行解,那么有可能会有比这更优的解,那么我们就去它的右边二分。为什么去右边?答案是,这个区间是递增的 ,而我们求的是最短跳跃距离的最大值,显然在右边的值肯定比左边大,那么我们就有可能找到比这更优的解,直到找不到,那么最后找到的解就认为是区间内最优解。反过来,如果二分到的这个解是一个非法解,我们就不可能再去右边找了。因为性质,右边的解一定全都是非法解。那么我们就应该去左边的区间找。接下来的问题就是实现check的功能,我们只需判断以这个距离为最短距离需要移走多少块石头,如果超出了题目给的限制条件,就是非法解,反之就是合法解,当它为合法解的时候,我们应该继续去右边二分。
模拟一下跳石头的过程,开始你在i=0的位置,判断当前的跳跃距离,如果这个跳跃距离比二分出来的跳跃距离小。那么就是个不合法的石头,应该移走,我们二分的是最短跳跃距离,已经是最短了,如果跳跃距离比最短岂不是显然不合法,移走之后要怎么做?先把计数器加上1,再考虑向前跳啊。去看移走之后的下一块石头,再次判断跳过去的距离,如果这次的跳跃距离比最短的长,那么这样跳是完全可以的,我们就跳过去,继续判断,如果跳过去的距离不合法就再拿走,这样不断进行这个操作。
下面是我的AC代码:
#include<stdio.h>
int L, N, M;
int a[50080];
int check(int x) {
int before = 0, num = 0;
for (int i = 1; i < N + 1; i++) {
if (a[i] - before < x)
num++;
else
before = a[i];
}
if (num > M)
return 0;
else
return 1;
}
int main() {
scanf("%d%d%d", &L, &N, &M);
for (int i = 1; i <= N; i++)
scanf("%d", &a[i]);
int l = 0, r = L, mid, ans;
while (r - l >= 0) {
mid = l + (r - l) / 2;
if (check(mid)) {
ans = mid;
l = mid + 1;
} else
r = mid - 1;
/* printf("%d %d",l,r);
printf("\n"); */
}
printf("%d", ans);
}
就题目的数据我手动模拟了一下二分的过程,一位大佬跟我说这种手动模拟非常有效!