算法 /

二分答案:先写判定,再找边界

二分答案不是在答案空间里碰运气:先证明单调性、写出 check 函数,再处理整数二分与溢出。

2026-10-27 约 3 分钟阅读
算法题解

二分答案的常见误解,是把它当成一个可以套用的循环模板。真正需要先想清楚的,是这一步:

如果答案 xx 可行,那么比它更宽松/更严格的答案是否也可行?

只有当这个问题有确定答案时,二分才成立。

把最优化问题变成判定问题

很多“最小化最大值”或“最大化最小值”的题目,直接求最优值很困难,但判断某个值 xx 是否可行相对容易。

设判定函数为 P(x)P(x)。如果 P(x)P(x) 在数轴上呈现单调性,例如:

P(x) 为真⟹P(x+1) 也为真P(x) \text{ 为真} \Longrightarrow P(x+1) \text{ 也为真}

那么就可以通过二分找到 PP 第一次为真的位置。

题目形式判定函数答案
最小化最大值能否让最大值不超过 xx最小的可行 xx
最大化最小值能否让最小值不小于 xx最大的可行 xx
可行性分配容量为 xx 时能否完成边界值

一个不依赖模板的写法

以“找到最小的可行值”为例,维护一个不可行下界和一个可行上界:

bool check(long long x) {
    // 判断 x 是否可行
    // ...
}

long long lo = L - 1; // 保证不可行
long long hi = R + 1; // 保证可行

while (hi - lo > 1) {
    long long mid = lo + (hi - lo) / 2;
    if (check(mid)) {
        hi = mid;
    } else {
        lo = mid;
    }
}

// hi 是第一个可行值
cout << hi << '\n';

这种写法的好处是边界含义明确:lo 始终不可行,hi 始终可行,循环结束时 hi 就是答案。

复杂度来自哪里

如果 check(x) 的复杂度是 O(n)O(n),二分的值域大小为 VV,总复杂度就是:

O(nlog⁡V)O(n \log V)

这里的“值域大小”不一定是数组长度,可能是题目给出的数值上限。不要把二分次数写死成 32 次,除非你已经确认值域范围。

三个容易出错的点

  1. 单调性没有证明。 样例能过不代表 check 满足单调性;先用小数据或交换论证检查。
  2. 区间端点含义混乱。 先决定 lo 与 hi 哪边可行,再让循环不变式贯穿始终。
  3. 中间值写出溢出。 习惯使用 lo + (hi - lo) / 2,并注意 check 内部也使用 64 位整数。

二分答案本身只是搜索方式。真正决定正确性的,是判定函数和单调性。

小结

看到“最小化最大值”“最大化最小值”“最小可行代价”时,可以先问三个问题:

  • 答案范围是什么?
  • 如何判断一个答案是否可行?
  • 这个判定是否单调?

三个问题都有清楚的回答之后,再写二分循环。