本文出自我的掘金博客, 欢迎大家访问传送门
二分答案算法
呀.二分答案
与二分查找
其实是不一样的二分答案: 即对你要求的答案进行二分
二分查找: 对一个已知的有序数据集上进行二分的查找
典型的使用场景: 要求我们求出某种条件的最大值的最小可能情况或者最小值的最大情况
使用前提:
1. 答案在一个固定的区间内
2. 难以通过搜索来找到符合要求的值, 但给定一个值你可以很快的判断它是不是符合要求
3. 可行解对于区间要符合单调性, 因为有序才能二分嘛
二分答案
法非常高效.STL
的函数lower_bound
与upper_bound
, 它存在的意义就是可以不用手写二分查找
了1.作用
这两个是STL中的函数,作用很相似:
假设我们查找x,那么:
lower_bound会找出序列中第一个大于等于x的数
upper_bound会找出序列中第一个大于x的数
没错这俩就差个等于号╮(╯▽╰)╭
2.用法
以下都以lower_bound做栗子 (因为upper_bound做出的栗子不好吃)
(其实就是我懒得打两遍)
它们俩使用的前提是一样的:序列是有序的
对于一个数组a,在[1,n)的区间内查找大于等于x的数(假设那个数是y),函数就写成:
lower_bound(a + 1, a + 1 + n, x);
函数返回一个指向y的指针
看着是不是很熟悉?回想sort使用的时候:
sort(a, a + 1 + n, cmp);
这里a+1,a+1+n的写法是不是很像?
STL里面的函数写区间一般都这个尿性
同样的,lower_bound和upper_bound也是可以加比较函数cmp的:
lower_bound(a + 1, a + 1 + n, x, cmp);
到这里不得不说说前面的"有序序列",这里的"有序"是对什么有序?
你可能已经猜到了,它是对于比较器有序,并且必须是升序!
比较器
改成greater ()
lower_bound(a + 1, a + 1 + n, x, greater () );
cmp
函数来自定义我们想要的顺序二分答案
法, 那么你也许会用搜索
,又或者会一个个的枚举
答案, 然后判断正确性, 那么将这里的枚举换成二分, 就是我们的二分答案
法啦!题目背景
一年一度的“跳石头”比赛又要开始了!
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。
在起点和终点之间,有 N块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。
输入格式
第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。
二分答案题目
, 很显然, 用暴力搜索
会超时, 而且我甚至不知道用暴力搜索
应该如何做, 有知道的童鞋可以教教我.m
块石头能否满足这个要求, 故满足应用条件2, 能很容易的判断一个值是否满足要求二分答案
法在一个区间上可能有很多个可行解, 这些解都满足要求, 但不一定是最优的, 我们要考虑所有的可行解, 并从中找到一个最优解作为我们的答案
M
进行比较, 若超过了, 那么就说明在最多移走M
块石头的限制条件下不能达到这个最短跳跃距离, 这就是一个不可行解
, 反之就是一个可行解
, 这下懂了吧! _可以去模拟这个跳石头的过程。开始你在i(i=0)位置,我在跳下一步的时候去判断我这个当前跳跃的距离,如果这个跳跃距离比二分出来的mid小,那这就是一个不合法的石头,应该移走。
为什么?我们二分的是最短跳跃距离,已经是最短了,如果跳跃距离比最短更短岂不是显然不合法,是这样的吧。移走之后要怎么做?先把计数器加上1,再考虑向前跳啊。去看移走之后的下一块石头,再次判断跳过去的距离,如果这次的跳跃距离比最短的长,那么这样跳是完全可以的,我们就跳过去,继续判断,如果跳过去的距离不合法就再拿走,这样不断进行这个操作,直到i = n+1,为啥是n+1?河中间有n块石头,显然终点在n+1处。(这里千万要注意不要把n认为是终点,实际上从n还要跳一步才能到终点)。
模拟完这个过程,我们查看计数器的值,这个值代表的含义是我们以mid作为答案需要移走的石头数量,然后判断这个数量 是不是超了就行。如果超了就返回false,不超就返回true。
judge
bool judge(int x) {
int tot = 0; //需要移动的总数
int i = 0;
int now = 0;//当前所在的石头
while(i < n + 1) {
i++;
//如果当前石头与下一块石头之间的距离比我们设定的最短的距离要小, 那么这块石头就得移走.
//tot++代表需要移动的加一, i++代表接下来判断移动的石头后面的一块与当前`now`石头的距离
if (a[i] - a[now] < x) {
//那么就将这块石头拿走
tot++;
} else {
//如果满足的话
now = i;
}
}
//如果需要移动比最大可移动数目还要多的石头, 那么return false
if (tot > m) {
return false;
} else {
//否则
return true;
}
}
AC
代码#include
#define maxn 500010
using namespace std;
int l, r, n, m, ans, mid, d;
int a[maxn];
bool judge(int x) {
int tot = 0;
int i = 0;
int now = 0;
while(i < n + 1) {
i++;
if (a[i] - a[now] < x) {
//那么就将这块石头拿走
tot++;
} else {
now = i;
}
}
if (tot > m) {
return false;
} else {
return true;
}
}
int main() {
cin >> d >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
a[n + 1] = d;
l = 1, r = d;
while (l <= r) {
//二分枚举
mid = (l + r) / 2;
if (judge(mid)) {
//mid是可行解的情况
ans = mid;
l = mid + 1;
}
//mid不是可行解的情况
else r = mid - 1;
}
cout << ans << endl;
return 0;
}