## 一期信息竞赛:总结·归纳·反思

## 学习内容

### 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;

}

```

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值