## 学习内容
### 1 二分查找和二分答案
### · 二分查找
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法.但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列.也就是说,二分查找要求被查找的数列有序.
二分查找的时间复杂度是O( $log_2N$ ).
二分查找需要范围(上界、下界).二分查找的范围即为上下界之间.所以查找中要根据查找的数来逐步缩小范围.下面是一个二分查找的例题:
#### 题目描述
三次方程$ax^3+bx^2+cx+d=0$的系数$a, b, c, d$及其中一个根的区间$x1 - x2$,求出方程在这一区间的一个实根.
#### 输入格式
六个实数,$a, b, c, d, x1, x2$.
#### 输出格式
输出一个数,方程在区间$x1 - x2$内的根。保留小数点后两位.
#### 样例
##### 样例输入
1 -5 16 -80 -10 10
##### 样例输出
5.00
#### 数据范围与提示
必须用二分法求解.
#### 示例代码
```c++
#include <cstdio>
#include <cmath>
using namespace std;
double A, B, C, D, X1, X2;
double Left, Right, Mid;
double Solve(double A, double B, double C, double D, double X) {
return (X * X * X * A) + (X * X * B) + (X * C) + D;
}
int main(void) {
scanf("%lf%lf%lf%lf%lf%lf", &A, &B, &C, &D, &X1, &X2);
Left = (X1 > X2 ? X2 : X1);
Right = X1 + X2 - Left;
Mid = (Left + Right) / 2;
while (fabs(Left - Right) > 1e-5) {
Mid = (Left + Right) / 2;
double Tmp = Solve(A, B, C, D, Left);
if (Solve(A, B, C, D, Mid) * Tmp > 0) Left = Mid;
else Right = Mid;
}
printf("%.2lf", Mid);
return 0;
}
```
C++标准库中$<algorithm>$头文件也提供了关于二分查找的函数$binary\_search()$, $upper\_bound()$, $lower\_bound()$,利用其可快速进行二分查找.
#### 1 $lower\_bound()$
```c++
lower_bound() 函数定义在<algorithm>头文件中,其语法格式有 2 种,分别为:
//在 [first, last) 区域内查找不小于 val 的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
const T& val);
//在 [first, last) 区域内查找第一个不符合 comp 规则的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
其中,first 和 last 都为正向迭代器,[first, last) 用于指定函数的作用范围;val 用于指定目标元素;comp 用于自定义比较规则,此参数可以接收一个包含 2 个形参(第二个形参值始终为 val)且返回值为 bool 类型的函数,可以是普通函数,也可以是函数对象。
实际上,第一种语法格式也设定有比较规则,只不过此规则无法改变,即使用 < 小于号比较 [first, last) 区域内某些元素和 val 的大小,直至找到一个不小于 val 的元素。这也意味着,如果使用第一种语法格式,则 [first,last) 范围的元素类型必须支持 < 运算符。
此外,该函数还会返回一个正向迭代器,当查找成功时,迭代器指向找到的元素;反之,如果查找失败,迭代器的指向和 last 迭代器相同。
再次强调,该函数仅适用于已排好序的序列。所谓“已排好序”,指的是 [first, last) 区域内所有令 element<val(或者 comp(element,val),其中 element 为指定范围内的元素)成立的元素都位于不成立元素的前面。
```
#### 2 $upper\_bound()$
```c++
upper_bound() 函数定义在<algorithm>头文件中,用于在指定范围内查找大于目标值的第一个元素。该函数的语法格式有 2 种,分别是:
//查找[first, last)区域中第一个大于 val 的元素。
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val);
//查找[first, last)区域中第一个不符合 comp 规则的元素
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
其中,first 和 last 都为正向迭代器,[first, last) 用于指定该函数的作用范围;val 用于执行目标值;comp 作用自定义查找规则,此参数可接收一个包含 2 个形参(第一个形参值始终为 val)且返回值为 bool 类型的函数,可以是普通函数,也可以是函数对象。
实际上,第一种语法格式也设定有比较规则,即使用 < 小于号比较 [first, last) 区域内某些元素和 val 的大小,直至找到一个大于 val 的元素,只不过此规则无法改变。这也意味着,如果使用第一种语法格式,则 [first,last) 范围的元素类型必须支持 < 运算符。
同时,该函数会返回一个正向迭代器,当查找成功时,迭代器指向找到的元素;反之,如果查找失败,迭代器的指向和 last 迭代器相同。
另外,由于 upper_bound() 底层实现采用的是二分查找的方式,因此该函数仅适用于“已排好序”的序列。注意,这里所说的“已排好序”,并不要求数据完全按照某个排序规则进行升序或降序排序,而仅仅要求 [first, last) 区域内所有令 element<val(或者 comp(val, element)成立的元素都位于不成立元素的前面(其中 element 为指定范围内的元素)。
```
#### 3 $binary\_search()$
```c++
该函数有 2 种语法格式,分别为:
//查找 [first, last) 区域内是否包含 val
bool binary_search (ForwardIterator first, ForwardIterator last,
const T& val);
//根据 comp 指定的规则,查找 [first, last) 区域内是否包含 val
bool binary_search (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
其中,first 和 last 都为正向迭代器,[first, last) 用于指定该函数的作用范围;val 用于指定要查找的目标值;comp 用于自定义查找规则,此参数可接收一个包含 2 个形参(第一个形参值为 val)且返回值为 bool 类型的函数,可以是普通函数,也可以是函数对象。
同时,该函数会返回一个 bool 类型值,如果 binary_search() 函数在 [first, last) 区域内成功找到和 val 相等的元素,则返回 true;反之则返回 false。
需要注意的是,由于 binary_search() 底层实现采用的是二分查找的方式,因此该函数仅适用于“已排好序”的序列。所谓“已排好序”,并不是要求 [first, last) 区域内的数据严格按照某个排序规则进行升序或降序排序,只要满足“所有令 element<val(或者 comp(val, element)成立的元素都位于不成立元素的前面(其中 element 为指定范围内的元素)”即可。
```
### · 二分答案
#### 二分查找与二分答案有何区别?
二分查找:在一个已知的有序数据集上进行二分地查找
二分答案:答案有一个区间,在这个区间中二分,直到找到最优答案
#### 什么是二分答案?
答案属于一个区间,当这个区间很大时,暴力超时。但重要的是——这个区间是对题目中的某个量有单调性的,此时,我们就会二分答案。每一次二分会做一次判断,看是否对应的那个量达到了需要的大小。
判断:根据题意写个check函数,如果满足check,就放弃右半区间(或左半区间),如果不满足,就放弃左半区间(或右半区间)。一直往复,直至到最终的答案。
#### 如何判断一个题是不是用二分答案做的呢?
1、答案在一个区间内(一般情况下,区间会很大,暴力超时)
2、直接搜索不好搜,但是容易判断一个答案可行不可行
3、该区间对题目具有单调性,即:在区间中的值越大或越小,题目中的某个量对应增加或减少。
此外,可能还会有一个典型的特征:求...最大值的最小 、 求...最小值的最大。
1、求...最大值的最小,我们二分答案(即二分最大值)的时候,判断条件满足后,尽量让答案往前来.
2、同样,求...最小值的最大时,我们二分答案(即二分最小值)的时候,判断条件满足后,尽量让答案往后走.
### 例题
#### 题目描述
对于给定的一个长度为N的正整数数列 $A_{1\sim N}$,现要将其分成 $M$($M\leq N$)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段。
将其如下分段:
$$[4\ 2][4\ 5][1]$$
第一段和为 $6$,第 $2$ 段和为 $9$,第 $3$ 段和为 $1$,和最大值为 $9$。
将其如下分段:
$$[4][2\ 4][5\ 1]$$
第一段和为 $4$,第 $2$ 段和为 $6$,第 $3$ 段和为 $6$,和最大值为 $6$。
并且无论如何分段,最大值不会小于 $6$。
所以可以得到要将数列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段,每段和的最大值最小为 $6$。
#### 输入格式
第 $1$ 行包含两个正整数 $N,M$。
第 $2$ 行包含 $N$ 个空格隔开的非负整数 $A_i$,含义如题目所述。
#### 输出格式
一个正整数,即每段和最大值最小为多少。
#### 样例 #1
##### 样例输入 #1
```
5 3
4 2 4 5 1
```
##### 样例输出 #1
```
6
```
#### 提示
对于 $20\%$ 的数据,$N\leq 10$。
对于 $40\%$ 的数据,$N\leq 1000$。
对于 $100\%$ 的数据,$1\leq N\leq 10^5$,$M\leq N$,$A_i < 10^8$, 答案不超过 $10^9$。
#### 示例代码
```c++
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n, a[1000001], m, sum[100001];
bool check(ll mid) {
int now = 0;
int t = 0;
for (int i = 2; i <= n; i++) {
if (sum[i] - sum[now] > mid) {
t++;
now = i - 1;
}
}
if (t >= m) return true;
else return false;
}
int main(int argc, char* argv[]) {
ios::sync_with_stdio(false);
cin >> n >> m;
ll r, l = 0;
cin >> a[1];
sum[1] = a[1];
for (int i = 2; i <= n; i++) {
cin >> a[i];
sum[i] = a[i] + sum[i - 1];
l = max(l, a[i]);
}
r = sum[n];
while (l <= r) {
ll mid = (l + r) / 2;
if (check(mid))
l = mid + 1;
else r = mid - 1;
}
cout << l;
return 0;
}
```