【W的AC企划 - 第三期】二分与三分算法

往期浏览

第一期 - 博弈论

第二期 - 前缀和

第三期 - 二分与三分算法

第四期 - 莫队算法

第五期 - 线段树(暂时未公开)

第六期 - 位运算 (Bitmasks)

第七期 - 树上分治

第八期 - Tarjan缩点

第九期 - 网络流

第十期 - 字符串哈希

讲解与模板

整数域二分算法

\(\mathcal O(\log N)\) 的复杂度使得其应用极为广泛,除了手写二分函数外,还有一些库函数可以使用:

  • lower_bound(x):返回大于等于 \(x\) 的第一个迭代器;
  • upper_bound(x):返回大于 \(x\) 的第一个迭代器;
  • binary_search(x):在容器中二分查找 \(x\) 是否存在。

我的旧版模板参考自Acwing,该版本无法计算负数,分为两个部分:

在递增序列 \(a\) 中查找 \(\geq x\) 数中最小的一个(即 \(x\)\(x\) 的后继):

while (l < r) {
  int mid = (l + r) / 2;
  if (a[mid] >= x) r = mid;
  else l = mid + 1;
}
return a[l];

在递增序列 \(a\) 中查找 \(\leq x\) 数中最大的一个(即 \(x\)\(x\) 的前驱):

while (l < r) {
  int mid = (l + r + 1) / 2;
  if (a[mid] <= x) l = mid;
  else r = mid - 1;
}
return a[l];

新的模板需要记录答案,也分为两个部分:

在递增序列 \(a\) 中查找 \(\geq x\) 数中最小的一个(即 \(x\)\(x\) 的后继):

while (l <= r) {
    int mid = (l + r) / 2;
    if (judge(mid)) {
        r = mid - 1;
        ans = mid;
    } else {
        l = mid + 1;
    }
}
return ans;

在递增序列 \(a\) 中查找 \(\leq x\) 数中最大的一个(即 \(x\)\(x\) 的前驱):

while (l <= r) {
    int mid = (l + r) / 2;
    if (judge(mid)) {
        l = mid + 1;
        ans = mid;
    } else {
        r = mid - 1;
    }
}
return ans;

实数域二分算法

引入了实数后需要考虑精度问题,目前主流的写法是限制二分次数,当然也可以使用浮点数比较法,这里两种都列出。

for (int t = 1; t <= 100; t++) {
    long double mid = (l + r) / 2;
    if (judge(mid)) r = mid;
    else l = mid;
}
cout << l << endl;
while (r - l > EPS) {
    long double mid = (l + r) / 2;
    if (judge(mid)) r = mid;
    else l = mid;
}
cout << l << endl;

整数域三分算法

三分的一大运用便是求解函数的极值点,虽然很高大上但是实际写起来并不复杂,基本与二分一致。

while (l < r) {
    int mid = (l + r) / 2;
    if (check(mid) <= check(mid + 1)) r = mid;
    else l = mid + 1;
}
cout << check(l) << endl;

实数域三分算法

同样有两种写法,这里展示限制次数法。

ld l = -1E9, r = 1E9;
for (int t = 1; t <= 100; t++) {
    ld mid1 = (l * 2 + r) / 3;
    ld mid2 = (l + r * 2) / 3;
    if (judge(mid1) < judge(mid2)) {
        r = mid2;
    } else {
        l = mid1;
    }
}
cout << l << endl;

题单与部分题解

注:二分的题实在太多了,此后我不再单独记录。

AcWing 789. 数的范围:整数域二分

题意:给出一个递增序列,然后有 \(Q\) 次询问,每个询问包含一个数字 \(x_i\) ,对于每个询问,输出 \(x_i\) 在序列中第一次出现的下标和最后一次出现的下标。

思路:建立模型,第一次出现的下标即为查找左边界的位置;最后一次出现的下标则同理。

P2678 [NOIP2015 提高组] 跳石头:整数域二分

题意:河上有一些石头,选手需要跳石头过河。现在,至多可以移走 \(K\) 块石头,请你输出最短跳跃距离的最大值。

思路:显然,移走越多的石头,答案一定越大。二分“跳跃距离”,找到符合条件(至多移走 \(K\) 块石头)的最大跳跃距离。建立模型,即为查找右边界。满足 l = mid 条件的情况是“跳跃距离小”,而跳跃距离越小移走的石头数量越少,所以满足该条件的情况是 judge[mid] <= K 。

P1182 数列分段 Section II:整数域二分

题意:有一个由正整数构成的序列,现在,你需要将这个序列划分为 \(K\) 段,使得每段和的最大值最小。

思路:二分“每段和的最大值”,找到符合条件(恰好划分成 \(K\) 段)的最小值。建立模型,即为查找左边界。注意,满足 r = mid 条件的情况是“和大”,而和越大段数越少,所以满足该条件的情况是 judge[mid] <= K

P1824 进击的奶牛:整数域二分

题意:有 \(N\) 个牛棚,已知每个牛棚的位置,现在需要将 \(M\) 头牛安置进去,请你给出一个最佳方案,使得牛们彼此的最近距离最大,直接输出这个值。

思路:二分“距离”,找到符合条件( \(M\) 头牛)的最大值。建立模型,即为查找右边界。注意,满足 l = mid 条件的情况是“距离小”,而牛越多距离越小,所以满足该条件的情况是 judge[mid] >= M

P7585 [COCI2012-2013#1] LJUBOMORA:整数域二分

题意:有 \(M\) 种颜色不同数量不同的弹珠要分给 \(N\) 个孩子,每个孩子得到的弹珠颜色必须相同,输出分到弹珠最多的孩子的最小值。

思路:二分“分到的弹珠数量”,找到符合条件(恰好分给 \(N\) 个孩子)的最小值。建立模型,即为查找左边界。注意,满足 r = mid 条件的情况是“分到的弹珠数量多”,而分到的越多分给的人数越少,所以满足该条件的情况是 judge[mid] <= N

P3853 [TJOI2007]路标设置:整数域二分

题意:路上有一系列路标,现在至多可以增设 \(K\) 个路标,使得相邻路标的最大距离最小,输出这个距离。

思路:显然,增设的路标越多,最大距离的最小值越小。二分“距离”,找到符合条件(至多增设 \(K\) 个路标)的最小值。建立模型,即为查找左边界。注意,满足 r = mid 的条件的情况是”距离大“,而距离越大增设的路标数量越少,所以满足该条件的情况是 judge[mid] <= K

P1873 [COCI 2011/2012 #5] EKO / 砍树:整数域二分

题意:已知 \(N\) 棵树的高度,你可以设定一个高度 \(H\) ,然后你可以得到高于 \(H\) 部分的木头。现在,至少需要砍伐 \(M\) 米长的木材,输出 \(H\) 的最大值。

思路:二分“高度 \(H\) ”,找到符合条件(至少得到 \(M\) 米木材)的最大值。建立模型,即为查找右边界。注意,满足 l = mid 条件的情况是”高度H小“,而高度越小得到的木材长度越长,所以满足该条件的情况是 judge[mid] >= M

past202010_i - ピザ:前缀和、实数域三分

由于要求解的函数式为 \(|X-Y|\)、且 \(X+Y\) 的值固定,显然,随着 \(X\) 上升,\(Y\) 不停下降,满足三分性质。

1059D:实数域三分

比较板的三分求极值,但是方程的推导需要花点时间,可以 参考

106E:实数域三分套三分套三分

思路不难,实现较为复杂,建议做做看,因为三分套三分的误差限定是很重要的知识点。

posted @ 2023-08-09 00:56  hh2048  阅读(100)  评论(0编辑  收藏  举报