??? note
-```
-`>> 1` 比 `/ 2` 速度快一些
-```
+ `>> 1` 比 `/ 2` 速度快一些
注意,这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序(如果把满足条件看做 $1$,不满足看做 $0$,至少对于这个条件的这一维度是有序的)。换言之,二分搜索法可以用来查找满足某种条件的最大(最小)的值。
要想使用二分搜索法来解这种「最大值最小化」的题目,需要满足以下三个条件:
-1. 答案在一个固定区间内;
-2. 可能查找一个符合条件的值不是很容易,但是要求能比较容易地判断某个值是否是符合条件的;
-3. 可行解对于区间满足一定的单调性。换言之,如果 $x$ 是符合条件的,那么有 $x + 1$ 或者 $x - 1$ 也符合条件。(这样下来就满足了上面提到的单调性)
+1. 答案在一个固定区间内;
+2. 可能查找一个符合条件的值不是很容易,但是要求能比较容易地判断某个值是否是符合条件的;
+3. 可行解对于区间满足一定的单调性。换言之,如果 $x$ 是符合条件的,那么有 $x + 1$ 或者 $x - 1$ 也符合条件。(这样下来就满足了上面提到的单调性)
当然,最小值最大化是同理的。
二分是非常好的搜索算法,许多线性查找题目都能够转换为二分答案。
-来看一看一道例题[Luogu P1873 砍树](https://www.luogu.org/problemnew/show/P1873),我们可以从1到1000000000(10亿)来枚举答案,但是这种朴素写法肯定拿不到满分,因为从1跑到10亿太耗时间。我们可以对答案进行1到10亿的二分,其中,每次都对其进行检查可行性(一般都是使用贪心法)。**这就是二分答案。**
+来看一看一道例题[Luogu P1873 砍树](https://www.luogu.org/problemnew/show/P1873),我们可以从 1 到 1000000000(10 亿)来枚举答案,但是这种朴素写法肯定拿不到满分,因为从 1 跑到 10 亿太耗时间。我们可以对答案进行 1 到 10 亿的二分,其中,每次都对其进行检查可行性(一般都是使用贪心法)。**这就是二分答案。**
下面就是例题的参考答案。
```c++
int a[1000005];
-int n,m;
-bool check(int k)//检查可行性,k为锯片高度
+int n, m;
+bool check(int k) //检查可行性,k为锯片高度
{
- long long sum=0;
- for (int i=1;i<=n;i++)//检查每一棵树
- if (a[i]>k)//如果树高于锯片高度
- sum+=(long long)(a[i]-k);//累加树木长度
- return sum>=m;//如果满足最少长度代表可行
+ long long sum = 0;
+ for (int i = 1; i <= n; i++) //检查每一棵树
+ if (a[i] > k) //如果树高于锯片高度
+ sum += (long long)(a[i] - k); //累加树木长度
+ return sum >= m; //如果满足最少长度代表可行
}
-int find(int x)
-{
- int l=1,r=1000000001;//因为是左闭右开的,所以10亿要加1
- while (l+1<r)//如果两点不相邻
- {
- int mid=(l+r)/2;//取中间值
- if (check(mid))//如果可行
- l=mid;//升高锯片高度
- else
- r=mid;//否则降低叶片高度
- }
- return l;//返回左边值
+int find(int x) {
+ int l = 1, r = 1000000001; //因为是左闭右开的,所以10亿要加1
+ while (l + 1 < r) //如果两点不相邻
+ {
+ int mid = (l + r) / 2; //取中间值
+ if (check(mid)) //如果可行
+ l = mid; //升高锯片高度
+ else
+ r = mid; //否则降低叶片高度
+ }
+ return l; //返回左边值
}
-int main()
-{
- cin>>n>>m;
- for (int i=1;i<=n;i++)
- cin>>a[i];
- cout<<find(m);
- return 0;
+int main() {
+ cin >> n >> m;
+ for (int i = 1; i <= n; i++) cin >> a[i];
+ cout << find(m);
+ return 0;
}
```
看完了上面的代码,你肯定会有两个疑问:
-1. 为何搜索区间是左闭右开的?
+1. 为何搜索区间是左闭右开的?
因为搜到最后,会这样(以合法的最大值为例):
合法的最小值恰恰相反。
-1. 为何返回左边值?
+1. 为何返回左边值?
如上图
画一下图好理解一些(图待补)
-- 如果 `mid` 和 `midmid` 在最大(小)值的同一侧:
- 那么由于单调性,一定是二者中较大(小)的那个离最值近一些,较远的那个点对应的区间不可能包含最值,所以可以舍弃。
-- 如果在两侧:
- 由于最值在二者中间,我们舍弃两侧的一个区间后,也不会影响最值,所以可以舍弃。
+- 如果 `mid` 和 `midmid` 在最大(小)值的同一侧:
+ 那么由于单调性,一定是二者中较大(小)的那个离最值近一些,较远的那个点对应的区间不可能包含最值,所以可以舍弃。
+- 如果在两侧:
+ 由于最值在二者中间,我们舍弃两侧的一个区间后,也不会影响最值,所以可以舍弃。
## 分数规划
### 二分法
比如说我们要求的是最小的,记 $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
$$