1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! 三分探索。

use std::ops::Range;

/// 三分探索で極値を探す。
///
/// # Notes
/// [`extremum`] を参照されたい。
///
/// # Examples
/// ```
/// use nekolib::algo::extremum_slice;
///
/// let buf = [1, 3, 4, 6, 5, 2, 0, 1, 3];
/// //         <------ f ------>
/// //                  <------ g ------>
///
/// let f = |&x: &i32| x * x;
/// assert_eq!(extremum_slice(&buf[..6], f), (3_usize, 36));
/// let g = |&x: &i32| -x;
/// assert_eq!(extremum_slice(&buf[3..], g), (3_usize, 0));
/// ```
pub fn extremum_slice<T: Ord>(
    buf: &[T],
    mut f: impl FnMut(&T) -> T,
) -> (usize, T) {
    extremum(0..buf.len(), |i| f(&buf[i]))
}

/// 三分探索で極値を探す。
///
/// 離散値の区間 $[l, r)$ において、以下を満たす $i$ および $f(i)$ を返す。
/// $$ f(i-1) < f(i)\\text{ and } f(i) > f(i+1). $$
///
/// # Requirements
/// 凸である。すなわち、ある $i$ が存在して以下の二つが成り立つ。
/// - ${}^\\forall j \\in [l, i)$ に対して $f(j) < f(j+1)$。
/// - ${}^\\forall j \\in [i, r-1)$ に対して $f(j) > f(j+1)$。
///
/// # Implementation notes
/// 連続値の場合における黄金比分割のように、Fibonacci
/// 数列に基づいて区間を分割していくため、関数値を使い回すことができる。
///
/// 関数 $f$ の呼び出し回数を、区間を三等分する素直な実装と比較する。
/// 三等分の実装では
/// $2\\cdot\\log_{3/2}(r-l)+O(1)$ 回(係数の $2$ に注意)であり、こちらは
/// $\\log_{\\varphi}(r-l)+O(1)$ 回である。
/// ただし $\\varphi$ は黄金比 $(1+\\sqrt{5})/2 = 1.618\\dots$ である。
/// $$ \\sqrt{3/2} < 1.225 < 1.618 < \\varphi $$
/// であり、$\\log$ の底は大きい方がうれしいので、こちらの実装の方が定数倍が軽い。
///
/// コード長はやや長くなるものの、単純な例での実測では三等分のものよりわずかに高速であった。
///
/// # Complexity
/// $F\_0 = 1$, $F\_1 = 2$, $F\_i = F\_{i-1} + F\_{i-2}$ ($i \\ge 2$) で定義される数列 $\\{F\_k\\}$ を考える。
/// 区間幅 $n$ がある $k$ に対して $n \\le F\_k$ と抑えられるとき、$f$ の呼び出しを高々
/// $k$ 回、関数値同士の比較を高々 $k-1$ 回行う。
/// なお、この $k$ は $\\lceil\\log\_{\\varphi}(n)\\rceil$ で抑えられる。
///
/// # Suggestions
/// 引数は `Range<usize>` を渡すことにしているものの、実際には
/// `RangeBounds<I: {integer}>` を渡せるようにする方がよさそう?
/// ただし、両端とも `Unbounded` であっては困りそう(特に多倍長を視野に入れる場合?)。
/// 多倍長だと `Copy` がないから、計算結果自体を使い回せても `.clone()` でつらい?
///
/// # Examples
/// ```
/// use nekolib::algo::extremum;
///
/// let buf = [1, 3, 4, 6, 5, 2, 0, 1, 3];
/// //         <------ f ------>
/// //                  <------ g ------>
///
/// let f = |i: usize| buf[i] * buf[i];
/// assert_eq!(extremum(0..6, f), (3_usize, 36));
/// let g = |i: usize| -buf[i];
/// assert_eq!(extremum(3..8, g), (6_usize, 0));
/// ```
///
/// ```
/// use nekolib::algo::extremum;
///
/// let n = 1500;
/// for k in 0..n {
///     let mut count = 0;
///     let f = |i| { count += 1; -(i as i32 - k as i32).abs() };
///     assert_eq!(extremum(0..n, f), (k, 0));
///     assert!(count <= 15);
/// }
/// ```
pub fn extremum<T: Ord>(
    Range { start, end }: Range<usize>,
    mut f: impl FnMut(usize) -> T,
) -> (usize, T) {
    let n = end - start;
    if n == 0 {
        panic!("range must be non-empty");
    }
    if n == 1 {
        return (start, f(start));
    }

    let (mut i0, mut i1) =
        std::iter::successors(Some((1, 2)), |&(i, j)| Some((j, i + j)))
            .find(|&(i, j)| i + j > n)
            .unwrap();
    let mut g = |i| if i <= n { Some(f(start + i - 1)) } else { None }; // None means -inf
    let mut d = i0;
    let mut g0 = g(i0);
    let mut g1 = g(i1);
    while d > 1 {
        match (g0, g1) {
            (Some(f0), Some(f1)) if f0 < f1 => {
                // |lo  i0 < i1     hi|
                //     |lo   i0 i1  hi|
                let tmp = i0 + d;
                i0 = std::mem::replace(&mut i1, tmp);
                g0 = Some(f1);
                g1 = g(i1);
            }
            (Some(f0), _) => {
                // |lo     i0 > i1  hi|
                // |lo  i0 i1   hi|
                let tmp = i1 - d;
                i1 = std::mem::replace(&mut i0, tmp);
                g1 = Some(f0);
                g0 = g(i0);
            }
            (None, _) => unreachable!(),
        }
        d -= i1 - i0;
    }

    match (g0, g1) {
        (Some(f0), Some(f1)) if f0 < f1 => (start + i1 - 1, f1),
        (Some(f0), _) => (start + i0 - 1, f0),
        (None, _) => unreachable!(),
    }
}

#[test]
fn extremum_count() {
    let mut fl = 1;
    let mut fr = 2;
    for i in 1..=15 {
        for n in fl..fr {
            for k in 0..n {
                let mut count = 0;
                let f = |i| {
                    count += 1;
                    -(i as i32 - k as i32).abs()
                };
                let res = extremum(0..n, f);
                assert_eq!(res, (k, 0));
                assert!(count <= i);
            }
        }
        let tmp = fl + fr;
        fl = std::mem::replace(&mut fr, tmp);
    }
}