## 二分搜索

二分搜索，也称折半搜索，是用来在一个有序数组中查找某一元素的算法。

以在一个升序数组中查找一个数为例。

它每次考察数组当前部分的中间元素，如果中间元素刚好是要找的，就结束搜索过程；如果中间元素小于所查找的值，那么左侧的只会更小，不会有所查找的元素，只需要到右侧去找就好了；如果中间元素大于所查找的值，同理，右侧的只会更大而不会有所查找的元素，所以只需要到左侧去找。

在二分搜索过程中，每次都把查询的区间减半，因此对于一个长度为 $n$ 的数组，至多会进行 $O(\log n)$ 次查找。

```c++
int binary_search(int start, int end, int key) {
  int ret = -1;       // 未搜索到数据返回-1下标  
	int mid;
	while (start <= end) {
		mid = start + ((end - start) >> 1); //直接平均可能会溢出，所以用这个算法
		if (arr[mid] < key)
			start = mid + 1;
		else if (arr[mid] > key)
			end = mid - 1;
		else {            // 最后检测相等是因为多数搜索情况不是大于就是小于
			ret = mid;  
      break;
    }
	}
	return ret;     // 单一出口
}
```

??? note
    `>> 1` 比 `/ 2` 速度快一些

注意，这里的有序是广义的有序，如果一个数组中的左侧或者右侧都满足某一种条件，而另一侧都不满足这种条件，也可以看作是一种有序（如果把满足条件看做 $1$，不满足看做 $0$，至少对于这个条件的这一维度是有序的）。换言之，二分搜索法可以用来查找满足某种条件的最大（最小）的值。

如果我们要求满足某种条件的最大值的最小可能情况（最大值最小化）呢？首先的想法是从小到大枚举这个作为答案的「最大值」，然后去判断是否合法。要是这个答案是单调的就好了，那样就可以使用二分搜索法来更快地找到答案。

要想使用二分搜索法来解这种「最大值最小化」的题目，需要满足以下三个条件：

1. 答案在一个固定区间内；
2. 可能查找一个符合条件的值不是很容易，但是要求能比较容易地判断某个值是否是符合条件的；
3. 可行解对于区间满足一定的单调性。换言之，如果 $x$ 是符合条件的，那么有 $x + 1$ 或者 $x - 1$ 也符合条件。（这样下来就满足了上面提到的单调性）

当然，最小值最大化是同理的。

二分法把一个寻找极值的问题转化成一个判定的问题（用二分搜索来找这个极值）。类比枚举法，我们当时是枚举答案的可能情况，现在由于单调性，我们不再需要一个个枚举，利用二分的思路，就可以用更优的方法解决「最大值最小」、「最小值最大」。这种解法也成为是「二分答案」，常见于解题报告中。

### STL 的二分查找

补充一个小知识点， 对于一个有序的 array 你可以使用 `std::lower_bound()` 来找到第一个大于等于你的值的数， `std::upper_bound()` 来找到第一个大于你的值的数

请注意，必须是有序数组，否则答案是错误的。

关于具体使用方法，请参见 STL 页面（施工中）。

## 三分法

```c++
mid = left + (right - left >> 1);
midmid = mid + (right - mid >> 1); // 对右侧区间取半
if (cal(mid) > cal(midmid))
  right = midmid;
else
  left = mid;
```

三分法可以用来查找凸函数的最大（小）值。

画一下图好理解一些（图待补）

-   如果 `mid` 和 `midmid` 在最大（小）值的同一侧：
    那么由于单调性，一定是二者中较大（小）的那个离最值近一些，较远的那个点对应的区间不可能包含最值，所以可以舍弃。

-   如果在两侧：
    由于最值在二者中间，我们舍弃两侧的一个区间后，也不会影响最值，所以可以舍弃。

## 分数规划

分数规划是这样一类问题，每个物品有两个代价 $c_i$，$d_i$，要求通过某种方式选出若干个，使得 $\frac{\sum{c_i}}{\sum{d_i}}$ 最大或最小。

经典的例子是 最优比率环、最优比率生成树 等等。

### 二分法

比如说我们要求的是最小的，记 $L$ 为最优的答案，对这个式子做一些变换：

$$
L \geq \frac{\sum{c_i}}{\sum{d_i}}
$$

把分母乘过去，把右侧化为 $0$：

$$
{\sum{d_i}} \times L - {\sum{c_i}} \geq 0
$$

即：

$$
{\sum_{i=1}^N{d_i}} \times L - {\sum_{i=1}^N{c_i}} \geq 0
$$

$$
\sum_{i=1}^N{d_i \times L - c_i} \geq 0
$$

不难发现，如果 $L'$ 比 $L$ 要小，上式左端的值会更大一些。

所以要求得最小的 $L$，我们要求的就变成了让上式左端最接近 $0$ 的 $L$。

不难发现左端的式子是随 $L$ 变化而单调变化的，所以可以通过二分法来解决。

### Dinkelbach 算法

Dinkelbach 算法是每次用上一轮的答案当做新的 $L$ 来输入，不断地迭代，直至答案收敛。
