一致検索だけじゃない！ニブタンの応用【競技プログラミング】

#　はじめに
- 以前、ソートされた配列の中に、とある要素が存在するかどうかを高速に検索する手法として、[二分探索(ニブタン)](https://qiita.com/kei_tnk/items/066b8e34521f2c03235a)を投稿した。
- 今回は、「一致する要素」以外にも、「最も近似する要素」等でもニブタンを利用して高速検索する例を投稿する。
- なお、言語は、最近マイナーであることを身にしみて感じる「swift」で書いてます。AtCoderのABCコンテストのD問題以降だと、ACの解答例にswiftのコードが無いことがよくあるんだよね...悲しいね

# おさらい
- ニブタン（一致検索）のコードは次のとおり。なお、引数として渡す配列は、昇順でソート済みの前提。ニブタンの計算量はO(logN)だけど、ソートの計算量がO(NlogN)だから、ソートだけで全探索の計算量O(N)を超えてしまう。よって、ニブタンを繰り返し行うときに、ニブタンによる高速化は有効だけど、検索を一回だけ行うなら、ニブタンで無く全探索を行った方が全体の計算量は小さくなる。
- 以前作ったニブタンのコード(一致検索)をInt以外でも使えるように一般化し、また、配列の最小値未満または最大値超であれば、そこで探索をせずに終わるショートカットを追加した。
```swift
//二分探索(ニブタン)
func nib<T:Comparable>(_ vs:[T],_ num:T)->Bool{ // vsは昇順とする

    var l = 0 // 探索範囲の左端のindex
    var r = vs.count - 1 // 探索範囲の右端のindex

    if num < vs[l] || vs[r] < num {return false} // 配列の範囲外の時は終わり
    
    while(true) {
    
        let i = ( l + r ) / 2 // 探索範囲の中間地点
    
        if vs[i] == num {
            return true // 発見 
        } else if vs[i] > num { // インデックスiの要素の値が検索値numより大きいとき
            r = i - 1 //　探索範囲の右端をインデックスiの左隣とする
        } else { 
            l = i + 1  //　探索範囲の左端をインデックスiの右隣とする
        }
        
        if ( l > r ) {return false} // 発見できないまま、捜索範囲の左右が逆転
    }
}

let a = [1,3,5,7,9,11,15,20] // 昇順
print(nib(a,9)) // true
print(nib(a,10)) // false

let b = ["a","d","f","g","j","n"] // 昇順
print(nib(b,"d")) // true
print(nib(b,"e")) // false
```

# 近似値対応
- 次は、一致しない場合でも、近似値を返せるようにコードを書き換える。
- 上記の一致探索では、Comparableプロトコルに属する方を対象としたけど、近似値を求めるコードでは、引き算をする必要があるため、加算減算が可能なプロトコル`AdditiveArithmetic`を型制約に追加したうえで、以下の通りとした。
```swift
//ニブタン（近似値対応）
func nib<T:Comparable & AdditiveArithmetic>(_ vs:[T],_ num:T)->(Bool,T){ // vsは昇順とする

    var l = 0 // 探索範囲の左端のindex
    var r = vs.count - 1 // 探索範囲の右端のindex
    
    if num < vs[l] {return (false,vs[l])} // 配列の下限未満の時は、下限が近似値
    if vs[r] < num {return (false,vs[r])} // 配列の上限超の時は、上限が近似値
    
    while(true) {
    
        let i = ( l + r ) / 2 // 探索範囲の中間地点
    
        if vs[i] == num {
            return (true,num) // 発見 
        } else if vs[i] > num { // インデックスiの要素の値が検索値numより大きいとき
            r = i - 1 //　探索範囲の右端をインデックスiの左隣とする
        } else { 
            l = i + 1  //　探索範囲の左端をインデックスiの右隣とする
        }

        // 発見できないまま、捜索範囲の左右が逆転
        if ( l > r ) {
            if vs[l] - num >= num - vs[r] {return (false,vs[r])} else {return (false,vs[l])}   
        } 
    }
}
```
- コードは上記のとおり、戻り値はタプル（一致する値の有無,近似値）とした。タプルの第1要素は元のニブタンと同じ、第2要素は近似値(第1要素がtrueの時は検索値)となる。
- なお、近似値が2つあるとき(例えば、検索値が8で配列が[7,9]のとき、7と9は2つとも近似値)、小さい方が近似値となるコードになっている。
- 下記は実行例。
```swift
　// 入力
let a = [1,3,5,7,9,12,15,20] // 昇順
for i in 7...15 {
    print(i,nib(a,i))
}

// 出力
7 (true, 7)
8 (false, 7)
9 (true, 9)
10 (false, 9)
11 (false, 12)
12 (true, 12)
13 (false, 12)
14 (false, 15)
15 (true, 15)


// 入力
import Foundation
let b:[Decimal] = [-0.49,-0.35,-0.3,-0.03,0.1,0.27,0.4,0.51]
for i in stride(from: Decimal(-0.5), through: Decimal(0.5), by: Decimal(0.1)) {
    print(i,nib(b,i))
}

// 出力
-0.5 (false, -0.49)
-0.4 (false, -0.35)
-0.3 (true, -0.3)
-0.2 (false, -0.3)
-0.1 (false, -0.03)
0 (false, -0.03)
0.1 (true, 0.1)
0.2 (false, 0.27)
0.3 (false, 0.27)
0.4 (true, 0.4)
0.5 (false, 0.51)
```
- まあ、お試しで作ってみたけど、使い道はあまりなさそう。次に紹介する、lower_boundとかの方が使える。というか、AtCoderでc++を使っている人は使いまくり。

# c++の「lower_bound」,「upper_bound」について
- 競技プログラミングでc++を使っている人が多用している、ニブタン利用の検索機能に`lower_bound`と`upper_bound`がある。これは、ともに、検索結果の「位置(index)」を戻り値とする。有無だけを返す最初のニブタンと違い、位置情報なので、単なる有無情報(1bit情報)より価値が高いと思う。
- なお、lowerとupperだから、真逆の機能かな？と思いきや、違いは「ほんのちょっと」なので、気を付ける必要がある。
- 例えば、配列の中身が、{10,10,30,30,30,40,70}の時に、検索値を「30」とした場合、
    - lower_bound ： 最初に検索値「以上」となるindexを返す。⇒ 「30以上」となる最初のindex「2」 を返す。
    - upper_bound ： 最初に検索値「超」となるindexを返す。⇒ 「30超」となる最初のindex「5」を返す。
- 具体的にc++のコードで示すと
```cpp
  auto a = {10,10,30,30,30,40,50,70,100,100};
  auto x = 30;

  auto lb = lower_bound(a.begin(), a.end(), x);
  auto ub = upper_bound(a.begin(), a.end(), x);
  
  cout << lb << " " << ub << endl; // 0x7fff732a7ca8 0x7fff732a7cb4
  cout << *lb << " " << *ub << endl; // 30 40
  
  cout << a.begin() << endl; // 0x7fff732a7ca0
  cout << ub - a.begin() << endl; // 5
  cout << ub - lb << endl; // 3    
```
- ちょ、待てよ！ lbが「2」じゃないじゃん！「0x7fff732a7ca8」って何だよ！
と思うのは、無理も無いですけど、スミマセン。わかりやすさを重視して、嘘つきました(ﾃﾍﾍﾟﾛ😛
- でも、ある要素iとある要素jのindex同士の「差異」が計算可能なので、上記コードでも、ubのindexと先頭のindex(a.begin())の差により、もし、aが配列であった場合のubのindexとなる「5」(= ub - a.begin())を取得できるから、実質、lbは「2」、ubは「5」と言ってしまっても過言では無い、と言えなく無くも無い。
- なお、検索値を100としてしまうと、upper_boundのindexが取得できない気がするけど、実際はindexは取得できる。でもインチキなindexだから、そのindexに格納されている値がおかしな数値となっている。じゃあ、なんでインチキindexを返すの？と思うけど、実は意味があるんです。後ほど説明します。
```cpp
  auto x1 = 100;

  auto lb1 = lower_bound(a.begin(), a.end(), x1);
  auto ub1 = upper_bound(a.begin(), a.end(), x1);
  
  cout << lb1 << " " << ub1 << endl; // 0x7ffe1abe3f00 0x7ffe1abe3f08
  cout << *lb1 << " " << *ub1 << endl; // 100 4200016
  cout << ub1 - lb1 << endl; // 2    
```
- 上記のとおり、lb1のindexは「0x7ffe1abe3f00」となり、そのindexにおける値\*lb1は「100」となっている。これに対し、本来は条件を満たす要素が無いにも関わらず、indexは「0x7ffe1abe3f08」が得られている。そのindexにおける値\*ub1については、「4200016」という意味の無い値となっているが、「ub1 - lb1」というindexの差が2であることにより、「検索値x1 = 100を満たす要素の数が2つある」という情報を得ることが出来た。よって、lower_bound、upper_boundの返り値(index)の条件として次が存在することが分かる
    - 【追加条件】lower_bound,upper_boundの検索値「以上」または「超」となる値を含まないとき、最後の要素のindexの「次のindex」を返す。

# swiftでlower_bound,upper_boundを実装する
- データを保持する構造体として、配列を使います。だから、indexも`0x7ffe1abe3f00`みたいな解読不明な値（メモリアドレスなのかな？）を使わず、0スタートのindexを使えます。そして、配列内に検索値「以上」「超」の要素がないときのindexは[要素数]とします。（普通、配列の最後の要素の番号は[要素数-1]）
- つくったコードは以下の通り。関数名は少し変えました。あと、whileループの条件を、r>lではなく、r-l>1に変えて、初期値もl=-1、r=vs.countにしています。これが、最もシンプルなコードっぽい。lower_bound、upper_boundを実装するときの定番の模様。
```swift
// lower_bound
func lbound<T:Comparable>(_ vs:[T],_ num:T)->Int{ // vsは昇順とする

    var l = -1 // 探索範囲の左端のindex
    var r = vs.count // 探索範囲の右端のindex
    
    while r - l > 1 {
        let m = (l + r) / 2
        if vs[m] >= num {r = m} else {l = m} //lower_bound
    }
    
    return r
}

// upper_bound
func ubound<T:Comparable>(_ vs:[T],_ num:T)->Int{ // vsは昇順とする

    var l = -1 // 探索範囲の左端のindex
    var r = vs.count // 探索範囲の右端のindex
    
    while r - l > 1 {
        let m = (l + r) / 2
        if vs[m] > num {r = m} else {l = m} //upper_bound
    }
    
    return r
}

```
- 動作チェックすると...うむ、いい感じ！
```swift
//入力
let a = [2,3,4] // 昇順

print("x i -- lower_bound")
for i in 0...5 {
    print(i,lbound(a,i))
}

print("x i -- upper_bound")
for i in 0...5 {
    print(i,ubound(a,i))
}

//出力
x i -- lower_bound
0 0
1 0
2 1
3 1
4 2
5 3

x i -- upper_bound
0 0
1 0
2 1
3 2
4 3
5 3
```
- 最初のc++のコード例も、同じ結果を再現できた!
```swift
let a = [10,10,30,30,30,40,50,70,100,100]
let x = 30

let lb = lbound(a, x)
let ub = ubound(a, x)
  
print(lb,ub) // 2 5
print(a[lb],a[ub]) // 30 40
print(ub - lb) // 3 -- 配列aの中の30の個数と一致 
```

# lboundとuboundを使って競技プログラミング問題を解いてみる
- ニブタン（近似値対応）では、「検索値に1番近い数値」を探すことが出来たけど、「検索値とk番目に近い数値との距離」を見つけることが出来るだろうか？
- そんな問題が[ABC364のd問題](https://atcoder.jp/contests/abc364/tasks/abc364_d)です。
- ここで突然ですが、配列aに対して、検索値「x-d」「x+d」を使ってubound,lboundの値を求める関数「f(d)=ubound(a,x+d) - lbound(a,x-d)」はどんな意味を持つ数字だろうか？
- ちなみに前項で例示したdがゼロの時、つまり、f(0)は配列中のxの個数でした。
- f(d)は、「配列中の、x-d以上、x+d以下の要素の数」になります。例えば、a = [1,2,2,6,8]で、x=4のとき、f(0)=0,f(1)=0,f(2)=3,f(3)=4,f(4)=5となります。
- ここで、当初の目的の「検索値とk番目に近い数値との距離」を考えると、f(d)>=kとなる最小のdを求めることと同じです。
- おや、「□ >= ○ となる最小の△」ってどこかで見たことない？おぁ、lboundと同じじゃん？
- △の位置にあるdが取り得る値は、0,1,2,3,4,....おお！indexっぽい！
- □の位置にあるf(d)は単純増加関数になってるから、ソート済みの配列と同じじゃん！
- でも、f(d)自体は配列ではないから、lbound関数を直接は使えないけど、lbound実装化の時のコードを使いまわせるね。
- ということで、方向性が分かったところでコードを書いてみよう...の前に、どのような形で入力されるか確認する。
```
4 3 // 配列Aの要素の数4，クエリの数
-3 -1 5 6 // 配列Aの各要素（座標）の値
-2 3 // クエリ① 座標「-2」から3番目に近い配列Aの座標までの距離
2 1 // クエリ② 座標「2」から1番目に近い配列Aの座標までの距離
10 4 // クエリ③ 座標「10」から4番目に近い配列Aの座標までの距離
```
- 上記はプログラムを書かずとも分かる。
    - クエリ①は、「−2」から「5」(3番目に近い座標)までの距離だから、出力は「7」となる。
    - クエリ②は、「2」から「-1」or「5」(1番近い座標)までの距離だから、出力は「3」となる。
    - クエリ③は、出力は「13」だね。
-　それじゃ、コードを書いてみよう
```swift
let (N,Q) = [readLine()!.split(separator:" ").map{Int($0)!}].map{($0[0],$0[1])}[0]
var A = readLine()!.split(separator:" ").map{Int($0)!}
A.sort()

//クエリ
for _ in 0..<Q {
    let (b,k) = [readLine()!.split(separator:" ").map{Int($0)!}].map{($0[0],$0[1])}[0]

    //lboundのコードをパクり
    var l:Int = -1 // 探索範囲の左端のindex
    var r:Int = 200_000_000 // 探索範囲の右端のindex

    //lboundの対象として、昇順配列の代わりに単調関数
    func f(_ d:Int) -> Int {
        let ub = ubound(A,b + d)
        let lb = lbound(A,b - d)
        return ub - lb
    }

    //見た目は、配列を対象としたlower_boundの実装コードと同じ
    while r - l > 1 {
        let m = (l + r) / 2
        if f(m) >= k {r = m} else {l = m} //lower_bound
    }
    
    print(r)
}

```
- 提出したら、600ms以下でACだった！やったね！
- とりあえず、swift教信者でも、lower_boundとupper_boundという武器を手に入れて、教プロで戦えることが分かったよ。

# おわりに
- 最初からlower_boundやupper_boundが用意されているc++はズルいなぁ
- Appleも、もう少し教プロ用のライブラリを充実させてほしいね。swift-collectionsはいつになったら、正式にswiftに取り込まれるんだろう。AtCoderでは使えるけど、paiza.ioとかで使えないから、不便だし、そもそも[HeapはAtCoderでも使えなかったし](https://qiita.com/kei_tnk/items/246b502200ee7cfb6ac2#comment-e4d243bbce599dcca6e4)。