前提知识:
二分法往下其实有一些小分支,最常见的是二分查找,然后就是二分答案,浮点数二分等等
主要谈谈二分查找和二分答案的具体区别,我们不能光报个菜名随口就来,一问具体也说不出来个所以然。
前提:看到题目如果有单调性并且有边界的话,那就基本上就是二分法了
简单来说
二分查找:是我们在单调且有界的一堆数字中,找到我们要找的数字
二分答案:是我们在已知答案范围中,不断二分排除逼近,找到最后唯一的答案
如跳石头这道题,它要求我们返回最短跳跃距离,我们可以知道最短跳跃距离这个答案的范围一定是在距离为1到距离为L之间(这里是关键!!!)
情况一:因为最短跳跃距离的最小值只能是1,即要跳的第一块石头就在起点距离为1的位置上
此时如果需要移走的石头块数为0,那么返回的答案就是1(这就是最小极限答案为1的例子)
也就是说原来中间放了N块岩石,我偷懒,一块也不搬,原先怎么样,现在就怎么样
如果最小极限答案为0,那不就是在起点不跳了嘛,明显不符合题意
情况二:至于最短跳跃距离的最大值只能是L,即要跳的第一块石头就在终点的位置上,此时如果需要移走的石头块数为N,那么返回的答案就是L(这就是最大极限答案为L的例子)
也就是说原来中间放了N块岩石,我犯病,又把放的N块岩石又给全搬走了,让选手直接从起点跳到终点
一旦搞清楚这道题是二分答案,而且左右两个边界也找到了的话,那么就简单了
注:
很多时候我们用二分法很容易在边界处理的时候出现错误
总结了以下的技巧,都可以避免出现边界错误
一:初始化时,左边界为第一个元素的位置减一,右边界为最后一个元素的位置加一
例如本题左边界就应该为0,右边界就应该为L+1
二:循环条件为while( left+1 != right )
三:循环跳出时会找到一条分界线来分开left和right(因为此时 left+1 == right )
四:最后随便选一个代入代码,例如选right,发现正确,那么答案就是right,发现错误,那么答案就是left
答案:
#include<stdio.h>
#define s 50001
int arr[s];
int L = 0, N = 0, M = 0;
int func(int mid) //判断此时的最小距离是否满足题目
{int count = 0, now = 0, i = 0; //i表示要跳的石头,now表示自己此时站的石头while (i < N + 1) //N+1是终点那块石头{i++;if (arr[i] - arr[now] < mid) //如果此时两点距离小于假设的最小距离{count++; //这块石头要搬}else //我就跳过去(有点贪心算法的感觉,不用管后面会怎么样,能跳一块是一块){now = i;}}if (count > M) //搬的石头过多,说明选择的最小距离大了{return 0;}else //搬的石头没超过M,说明选择的最小距离可能小了或者刚好{return 1;}
}
int main()
{scanf("%d%d%d", &L, &N, &M);for (int i = 1; i <= N; i++) //输入N个石头距离起点的长度{scanf("%d", &arr[i]);}arr[0] = 0; //初始化起点arr[N + 1] = L; //初始化终点int l = 0, r = L + 1; //二分答案的左边界和右边界while (l + 1 != r){int mid = (l + r) / 2;if (func(mid)) //选的最小距离小了{l = mid; //让最小距离更大一些}else //选的最小距离大了{r = mid; //让最小距离更小一些}}if (func(r)) //如果分界线右边的值代入进去正确{printf("%d\n", r);}else //说明分界线右边的值是错误的,分界线左边的值才是对的{printf("%d\n", l);}return 0;
}