自分用の 二分探索ライブラリ のメモです.

1. 整数版

以下では,INT は,int, long long, unsigned int などを表す.

signature

template <typename INT>
requires integral<INT>
INT binsearch(auto check, INT yes, INT no)

引数

  • check … 判定関数.INT を受け取って bool を返す.
  • yes … 真になる値
  • no … 偽になる値

制約

check 関数は,以下のいずれかを満たす述語 $P(x)$ を,開区間 (yes, no) において実装したものでなければならない

  • ある $t$ が存在して,
    • $x \leq t$ である $x$ について,$P(x)$ は真.
    • $t < x$ である $x$ について,$P(x)$ は偽.
  • ある $t$ が存在して,
    • $x \leq t$ である $x$ について,$P(x)$ は偽.
    • $t < x$ である $x$ について,$P(x)$ は真.

概念的には,$P(\text{yes})$ は真で,$P(\text{no})$ は偽でなければならない. ただし,実際には,check(yes)check(no) は呼ばれない.

返却値

binsearch(check, ...) が返す値 x について,

  • check(x) は true を返す.
  • yes < no のとき,check(x + 1) は,false を返す.
  • yes > no のとき,check(x - 1) は,false を返す.

使用例:

auto check = [](ll x) -> bool { return x * x <= 20000; }
ll x = binsearch(check, 100LL, 200LL); //  x <- 141
// または,binsearch<ll>(check, 100, 200);   INT の型は yes や no から推論される.

実装では,不要なオーバーフローを起こさないよう,(yes + no) / 2 ではなく,no + (yes - no) / 2 と書いている.

2. 浮動小数点版

以下では,DOUBLE は,float, double, long double などを表す.

signature

template <typename DOUBLE>
requires floating_point<DOUBLE>
DOUBLE binsearch(auto check, DOUBLE yes, DOUBLE no, DOUBLE err, bool abs_only = false);

引数

  • check … 判定関数.DOUBLE を受け取って bool を返す.
  • yes … 真になる値
  • no … 偽になる値
  • err … 誤差
  • abs_only … false (default) の場合には,相対誤差または絶対誤差が指定の値以下になる. true の場合には絶対誤差のみ.

制約

整数版と同じ

返却値

binsearch(check, ...) が返す値 $x$ は,制約のところで述べた $t$ との誤差が err 未満であるか, または,$t$ との誤差が err 未満であるような DOUBLE で表現可能な値が無い場合には,表現可能な値で $t$ に最も近い値である.したがって,check(x) が true を返す保証はない.

実装

最大で $\lceil\log_2 (| Y - N | / \text{err})\rceil + 1$ 回のループを回す. ここで,$Y$ と $N$ は,yes と no の引数で与える値. ループ各回で,相対誤差をチェックする.

使用例:

auto check = [](double x) -> bool { return x * x <= 2; }
double x = binsearch(check, 1.0, 2.0, 1e-6); // x <- 1.4142...