简单二分

如果你被「左闭右开二分」、「左闭右闭二分」等刁钻问题纠结的很烦恼,不妨看一下这篇博客。希望这篇博客能让你再也不用纠结于这些刁钻问题。

引入

先来个瞎编的例题

  • 交互。有一个长为 \(N\)01 字符串 \(S\),下标从 \(1\)\(N\)\(1 \leq N \leq 10^6\))。
  • 你只知道 \(N\) 的值,而不知道 \(S\) 具体是什么样。
  • 保证存在一个位置 \(p\)\(1 \leq p < N\))使得,\(S_1\)\(S_{p}\) 全为 0\(S_{p+1}\)\(S_N\) 全为 1
  • 每次可以询问一个 \(S_i\) 的值。需要在 \(30\) 次操作内找到 \(p\) 是多少。

也就是说 \(S\) 一定形如 \(\mathtt{00111111}\) 这样,并且 \(S_1\) 必定为 0\(S_N\) 必定为 1

我们考虑如下步骤:

  1. 使用两个变量 \(l\)\(r\)\(l\) 初始为 \(1\)\(r\) 初始为 \(N\)
  2. \(l = r - 1\) 时,结束。否则进行下一步。
  3. 令变量 \(mid\)\(\left \lfloor \dfrac{l + r}{2} \right \rfloor\)。若 \(S_{mid}\)0,将 \(l\) 更新为 \(mid\)。否则(\(S_{mid}\)1),将 \(r\) 更新为 \(mid\)
  4. 重新回到第二步。

举个具体例子。假设 \(N\)\(12\),字符串 \(S\)\(\mathtt{000011111111}\)

我们用红色代表 \(l\) 指向的位置,蓝色代表 \(r\) 指向的位置:

\[\mathtt{{\color{red}0}0001111111{\color{blue}1}} \]

此时\(l = 1\)\(r = 12\)\(mid = \left \lfloor \dfrac{1 + 12}{2} \right \rfloor = 6\)

\[\mathtt{{\color{red}0}0001{\color{orange}1}11111{\color{blue}1}} \]

由于 \(S_{mid}\)1,将 \(r\) 更新为 \(mid\)

\[\mathtt{{\color{red}0}0001{\color{blue}1}111111} \]

此时\(l = 1\)\(r = 6\)\(mid = \left \lfloor \dfrac{1 + 6}{2} \right \rfloor = 3\)

\[\mathtt{{\color{red}0}0{\color{orange}0}01{\color{blue}1}111111} \]

由于 \(S_{mid}\)0,将 \(l\) 更新为 \(mid\)

\[\mathtt{00{\color{red}0}01{\color{blue}1}111111} \]

此时\(l = 3\)\(r = 6\)\(mid = \left \lfloor \dfrac{3 + 6}{2} \right \rfloor = 4\)

\[\mathtt{00{\color{red}0}{\color{orange}0}1{\color{blue}1}111111} \]

由于 \(S_{mid}\)0,将 \(l\) 更新为 \(mid\)

\[\mathtt{000{\color{red}0}1{\color{blue}1}111111} \]

此时\(l = 4\)\(r = 6\)\(mid = \left \lfloor \dfrac{4 + 6}{2} \right \rfloor = 5\)

\[\mathtt{000{\color{red}0}{\color{orange}1}{\color{blue}1}111111} \]

由于 \(S_{mid}\)1,将 \(r\) 更新为 \(mid\)

\[\mathtt{000{\color{red}0}{\color{blue}1}1111111} \]

此时 \(l = r - 1\),步骤结束。

之后我们来分析一下 \(l\)\(r\) 的位置有什么意义。

在一开始的时候,\(l\) 指向 0\(r\) 指向 1

每次根据 \(mid\) 移动完 \(l\)\(r\) 后,\(l\) 依旧指向 0\(r\) 还是指向 1

而当步骤结束的时候,\(l\)\(r\) 刚好贴到一起了。这样一来,此时 \(l\) 所指向的,恰好是最后一个为 0 的位置,而 \(r\) 指向第一个 \(1\) 的位置。

而题目要我们求的,就是最后一个 0 的位置。这样一来,我们把最后得到的 \(l\) 拿出来即可。

进一步

如果上述题目当中,\(S\) 有可能全是 0 或者全是 1,应该怎么办?

我们可以修改一下上述算法。让 \(l\) 初始指向 \(0\)\(r\) 初始指向 \(N + 1\)。然后正常做即可。

如果全 0,结束之后 \(l = N\)\(r = N + 1\)

如果全 1,结束之后 \(l = 0\)\(r = 1\)

总之也是很符合直觉的。

你无需担心在过程中访问到 \(1 \sim N\) 以外的 \(S\) 的位置。

感性上理解,因为我们一开始就假定了 \(0\) 的位置一定是 0\(N+1\) 的位置一定是 1,我们不需要再去 check 一遍它到底是不是。

或者从式子上看,由于每次取 \(mid\) 之前,有 \(l < r - 1\) 成立。因此有 \(l < mid < r\)。整个过程中 \(l\) 不会比 \(0\) 小,\(r\) 不会比 \(N+1\) 大,因此 \(mid\) 无法取到 \(0\) 或者是 \(N + 1\)

总结一下思路

\(l\)\(r\),两个指针,一个只指向 \(0\),一个只指向 \(1\)(这里 \(0\)\(1\) 指的是,用来分界的条件是否成立),这样结束(也就是 \(l\)\(r\) 贴贴)之后,不管是想要最后一个 \(0\) 还是第一个 \(1\) 都可以直接拿。十分无脑。

细节

  • 注意两边到底是什么性质。别更新反了。
  • 注意初始情况下 \(l\)\(r\) 一定要指对地方。如果实际有效范围(比如上述题目里的 \([1, N]\))你无法保证端点(\(1\)\(N\))的性质的话,就像上面的例题一样,额外往外推一下,相当于让 \(l\)\(r\) 先初始指向人为设置的哨兵节点。

还是例题

之后简单再举个例子。

CF2132E Arithmetics Competition

题目大意:

  • 给定两堆牌 A 和 B,每张牌都有权值。两堆牌的权值分别记作 \([a_1, a_2, \ldots, a_n]\)\([b_1, b_2, \ldots, b_m]\)\(1 \leq n,m \leq 2 \times 10^5\)
  • \(q\) 次询问。\(q \leq 10^5\)。每次询问三个非负整数 \(x\)\(y\)\(z\),问,从牌堆 A 选出不超过 \(x\) 张牌,牌堆 B 选出不超过 \(y\) 张牌,总共不超过 \(z\) 张牌的情况下,权值之和最大可以是多少。\(0 \leq x \leq n\)\(0 \leq y \leq m\)\(0 \leq z \leq x + y\)

朴素的贪心是,把两堆牌混合在一起,从大到小排序然后取。考虑如何优化这一过程。

观察一下发现,在上述过程中,在 \(x\)\(y\)\(z\) 有一个减为 \(0\) 之前,一定是遇到的牌都能选。在此之后,\(x\)\(y\)\(z\) 当中不管是哪个减为 \(0\),你都只能在至多一堆牌当中去选。

假定两堆牌合并后得到长度为 \(n + m\) 的数组。

我们先去二分一个 \(pos\),使得 \([1, pos]\) 的数可以全选,而 \([1, pos+1]\) 的无法全选。前缀和维护一下前缀有多少个 A 的牌,多少个 B 的牌即可。

  • \(l\) 初始为 \(0\)\(r\) 初始为 \(z + 1\)
  • 左边具有性质:\([1, mid]\) 当中的 A 牌数量 \(\leq x\) 且 B 牌数量 \(\leq y\)
  • 右边性质则是它的反。
  • 结束后取出 \(l\) 作为 \(pos\)

之后,对于 \(pos+1\) 及以后的位置,之后如果还能选牌,那么要么只能选 A,要么只能选 B。以 A 为例。我们要找一个位置 \(end\),使得:\([pos+1, end]\) 当中的 A 可以全选,\([pos+1, end+1]\) 中的 A 没法全选。

  • \(l\) 初始为 \(pos\)\(r\) 初始为 \(n + m + 1\)
  • 左边具有性质:\([1, mid]\) 当中的 A 的数量 \(\leq min \{ x, z \}\)
  • 右边性质则是它的反。
  • 结束后取出 \(l\) 作为 \(end\)

即可轻松简单的解决这个题。

代码:https://codeforces.com/contest/2132/submission/334851310

posted @ 2025-11-12 23:33  Frost_Ice  阅读(0)  评论(0)    收藏  举报