第22回アルゴリズム勉強会
@Cafe IKAGAWADO 2014/7/6
アルゴリズム勉強会とは?
• 勉強会のコンセプト



- 知的好奇心を満たす

- 技術力を高める(アイディア力)

- 大学や社会人の垣根を超えた交流

- 知識やアルゴリズムを共有する場

 を目指す

注意事項
• 言語や開発環境は自由です。
• お好きな言語で実装してください。
"
• ただし、資料はJavaになりますのでご了承ください。
• (スライドの中では省略)
3
注意事項
• 特別、休憩時間は取りません。

1テーマごとに15分毎休憩時間を設けるつもりですが、

自由に休憩時間をとってください。

• 参加費(会場使用費・ドリンク代)は500円となっております。

ドリンクバー形式になってますので、お好きに飲んでください
• TopCoderレーティング割が適用されます。
• グリーンコーダー   400円
• ブルーコーダー    200円
• イエローコーダー 以上 0円 4
範囲の探索
今日は、AtCoderの問題を解く感じではなく

今日はテーマに対して深く勉強します。
今日はいつもより難しい

アルゴリズムを行います!!
6
純粋な実装を超える!
7
範囲の探索
• ソートされてる範囲[a,b)から値を高速に探そう
• 二分探索
"
• 範囲[a,b)の最小値を高速に探そう RMQ
• Segment Tree
"
• 範囲[a,b)の和を高速に求めよう
• BIT (Binary Indexed Tree)
範囲[a,b) から値を高速に探そう
9
問題
• ソートされてる配列がlist与えられてその長さが2つ以上の区間 [a,b+1)内に

値(list[a]+list[b])/2 (少数切り捨て)が含まれてるような範囲の個数を求め
てください。
"
• [a,b+1) は インデックスa以上b+1未満の範囲 (0-based Index)
• typoじゃないよ
"
• 例:[1,2,5] の場合  [1,2],[1,2,5],[2,5] なので1 

実装してみましょう
11
純粋な実装では
"
"
"
• a~bまで順番に見ていくので 最大 O(n^3) かかります。
"
• 探索部分を二分探索にすることで O(n^2 log n) 

で可能になる。
12
二分探索
"
"
そのほうめんでは「にぶたん」とか言われてる
二分探索
5 7 12 16 18 20 24 28
a b
c
16
16<18
c=(a+b)/2
二分探索
5 7 12 16 18 20 24 28
a b
16
16>12
c
二分探索
5 7 12 16 18 20 24 28
a b
16
16==16
c
リストの中に値

がなくても

使えます。
17
二分探索
5 7 12 16 18 20 24 28
a b
14
14<16
c
二分探索
5 7 12 16 18 20 24 28
a b
14
これ以上縮められなくなったら終了

(aまたはbが変わらなくなったら)

みつからなかったということになる。
c 14>12
二分探索 まとめ
• 範囲を半分半分に縮めてしぼっていく。
"
• リストがソート済みのものに可能。 

ソートしても良い場合 O (n log n)でソートしてもOK
"
• ある値を探す以外とかの応用など可能である。



条件を満たす値を探すなどの応用もある。

20
Segment Tree
21
問題
• 配列でlist与えられて[a,b)内の最小値を求めてください
• [a,b) は インデックスa以上b未満の範囲 (0-based Index)

• 例:[5,1,2,4,5] の 範囲[2,4)  での最小値は 1
"
"
• 課題では ありうるすべての範囲と

その最小値の和を求めてください。
実装してみましょう
23
• 以下の2つをすることをRMQ (Range Minimum/Max Query)と
言います。
• ある範囲の中から最小値を探す処理
• ある範囲の最小値を更新する処理



• RMQというと、たいてい上の2つの処理を何度もする。
• 純粋な実装の場合
• 更新する処理 list[x]=value で O(1)
• 探す処理   for(int i=a,i<b;i++) で O(n)
24
Segment Tree
25
26
min(a[0]~a[7])
a[0]~a[3] a[4]~a[7]
a[0]~

a[1]
a[2]~

a[3]
a[0] a[1] a[2] a[3]
a[4]~

a[5]
a[6]~

a[7]
a[4] a[5] a[6] a[7]
それぞれ範囲の最小値とする
データ構造
27
28
b[1]
b[2] b[3]
b[4] b[5]
b[8] b[9] b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
データの作り方
29
Segment Treeの作り方
• 完全二分木(にした方がいい)のでノード数を調整する。
• 一番下(根)のノード数が配列数より同じか大きくとる
• 配列数が5個なら
• 1+2+4+8 =15 個の要素の配列を用意する。
• すべての要素を最大値で埋める
30
31
b[1]
b[2] b[3]
b[4] b[5]
b[8] b[9] b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
元の配列 a[0]=5 をセットする場合
index=(ノード数)-8+0
更新
32
b[1]
b[2] b[3]
b[4] b[5]
5 b[9] b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
元の配列 a[0]=5 をセットする場合
b[8]=min(b[8],5)
更新
33
b[1]
b[2] b[3]
b[4] b[5]
5 b[9] b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
元の配列 a[0]=5 をセットする場合
index=

index/2
更新
34
b[1]
b[2] b[3]
5 b[5]
5 b[9] b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
元の配列 a[0]=5 をセットする場合
index=

index/2
更新
35
5
5 b[3]
5 b[5]
5 3 b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
元の配列 a[1]=3 をセットする場合
更新
36
5
5 b[3]
3

min(5,3)
b[5]
5 3 b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
元の配列 a[1]=3 をセットする場合
更新
index=

index/2
37
3
3 b[3]
3

min(5,3)
b[5]
5 3 b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
元の配列 a[1]=3 をセットする場合
更新
38
3
3 8
3 4
5 3 4 20
10 8
10 12 8 9
すべてのセットを行ったら(例えば)
範囲の探索
"
"
再帰を利用します。
ちょっと難しいです。
39
40
3
3 8
3 4
5 3 4 20
10 8
10 12 8 9
[1,5) の範囲に対して検索を行う時
41
3
3 8
3 4
5 3 4 20
10 8
10 12 8 9
[1,5) の範囲に対して検索を行う時
42
3
3 8
3 4
5 3 4 20
10 8
10 12 8 9
[1,5) の範囲に対して検索を行う時
43
3
3 8
3 4
5 3 4 20
10 8
10 12 8 9
[1,5) の範囲に対して検索を行う時
44
[1,5) の範囲に対して検索を行う時
getMin(int left,int right,int a,int b,int index)
"
のような関数を作る。

・範囲 [a,b)の中に [left,right)が完全に含ま
れている時 

 b[index]を返す。
"
"
4
3 10
45
[1,5) の範囲に対して検索を行う時
getMin(int left,int right,int a,int b,int index)
"
のような関数を作る。

・範囲 [a,b)が[left,right)が全く含まれてない
時

 無効な値を返す。(例えばMAX_INTEGER)
"
"
46
[1,5) の範囲に対して検索を行う時
getMin(int left,int right,int a,int b,int index)
"
のような関数を作る。

・範囲 [a,b)が[left,right)が一部でも含まれてたら

再帰的に関数を2回呼び出す。
(その最小値を計算する)
"
int m1=getMin(left,(left+right)/2,a,b,index*2) 

int m2=getMin((left+right)/2,right,a,b,index*2+1)
return min (m1,m2)
"
47
3
3 8
これに相当します。
a b
left right
ワンポイント
• [a,b) の理由  ——- [0,4) だとする
• もし [a,b] だと 領域を分割する時
• [0,3] だと [0,1],[2,3] としないといけない
• [0,4)とすると
• [0,2) ,[2,4) とかけて少しスマート
"
• これに習い、stringのsubstringなども [a,b) になっている。
48
ワンポイント
• Segment Treeの配列が1から始まってる理由
"
• ノードの下をたどるときに index*2+1, index*2+2

にしないといけないため、操作が少し面倒。
"
• 1からにすると index*2,index*2+1 にできてスマート
49
計算量
• xi=vを入れる       O(log n)
• [a,b)の最小値を調べる   O(log n)
"
• 上はいつでも任意の順番でいつでも行える。





(最初Treeを構築したら終わりではない)
50
範囲[a,b)の和を高速に求めよう
51
問題
• 配列でlist与えられて[a,b)内の合計値を求めてください
• [a,b) は インデックスa以上b未満の範囲 (0-based Index)

• 例:[5,1,2,4,5] の 範囲[2,4)  での合計値は 3
"
"
• 課題では ありうるすべての範囲の和を求めてください。
ヒント
53
Segment Treeを使います
54
55
sum(a[0]~a[7])
a[0]~a[3] a[4]~a[7]
a[0]~

a[1]
a[2]~

a[3]
a[0] a[1] a[2] a[3]
a[4]~

a[5]
a[6]~

a[7]
a[4] a[5] a[6] a[7]
それぞれ範囲の和とする
56
5
5 b[3]
8

5+3
b[5]
5 3 b[10] b[11]
b[6] b[7]
b[12] b[13] b[14] b[15]
元の配列 a[1]=3 をセットする場合
minの

ところを和

に変える
index=

index/2
57
67
32 39
8 24
5 3 4 20
22 17
10 12 8 9
すべてのセットを行ったら(例えば)
58
[1,6) の範囲に対して検索を行う時
getSum(int left,int right,int a,int b,int index)
"
のような関数を作る。

・範囲 [a,b)が[left,right)が一部でも含まれてたら

再帰的に関数を2回呼び出す。
"
int m1=getSum(left,(left+right)/2,a,b,index*2) 
int m2=getSum((left+right)/2,right,a,b,index*2+1)
return m1+m2
"
これでも大丈夫
59
更に良い方法があります
60
例えば
• 範囲[2,4) の部分和を求めるとき、



S(n) を [0,n)の部分和の関数とすると





S ( x ) - S ( y ) で表すことができる 。
"
"
• xとyの値は何でしょう?
61
範囲[2,4) の部分和は



S(4) - S (2) で求めることが出来ます。
62
Sはどうやって作る?
63
BIT
Binary Indexed Tree
64
65
67
32 39
8 24
5 3 4 20
22 17
10 12 8 9
66
67
32
8
5 4
22
10 8
データ構造
67
68
b[8]
b[4]
b[2]
b[1] b[3]
b[6]
b[5] b[7]
indexの性質
69
70
b[8]
b[4]
b[2]
b[1] b[3]
b[6]
b[5] b[7]
各 index番号は 各範囲の最後の要素になっている
71
b[1000]
b[100]
b[10]
b[1] b[11]
b[110]
b[101] b[111]
各 index番号を2進数で表現したときを最後から連続する0の数が高さの同じになる。
データの作り方
72
BITの作り方
• これもやっぱりノード数を調整する。
• 配列数が5個なら
• 1,2,4,8 で ノード数8にする。
"
"
• 初期化作業が大事
73
74
b[1000]
b[100]
b[10]
b[1] b[11]
b[110]
b[101] b[111]
a[0] = 5 のとき
更新対象
更新対象
更新対象
更新対象
75
b[1000]
b[100]
b[10]
b[1] b[11]
b[110]
b[101] b[111]
a[4] = 3 のとき
更新対象
更新対象
更新対象
76
b[1000]
b[100]
b[10]
b[1] b[11]
b[110]
b[101] b[111]
更新の求め方は, 対象のindex +1
の2進数表現の最後のbitを足すことで求められる
更新対象
更新対象
更新対象
データの探し方
77
78
b[1000]
b[100]
b[10]
b[1] b[11]
b[110]
b[101] b[111]
S(5) = [0,5) の部分和を求める時
"
元の配列とBITはindexが1ずれてるのに注意
79
b[1000]
b[100]
b[10]
b[1] b[11]
b[110]
b[101] b[111]
S(5) からBITでは 5が最後の要素で2進数表現では
101 なので b[101] の要素を見る  

次に b[100]の要素を見れれば良い  
80
b[1000]
b[100]
b[10]
b[1] b[11]
b[110]
b[101] b[111]
この求め方は 各ビットで1があるものを下から順番に0にしたもの
なっている 101 -> 100 など
うまくできてます。
81
ビットテクニック!
"
"
他でも使えるビットテクニックです。
"
忘れても普通にforなどでしてもいいと思います。
82
最後の1になってるbitを0にする
• 元の値   14 -> 1110
"
• 1を引く  13 -> 1101
"
• bit andを取る -> 1100

83
できました!
最後の1になっているものだけ取り出す
• 元の値   14 ->    1110
"
• 1を引く  13 ->    1101
"
• bit andを取る ->    1100
"
• 元の値と bit xorを取る  0010

84
できました!
このテクニックを使えば

BITが使えます。
85
[a,b)の和を求めるには?
• bit.getSum(a)で[0,a)
• bit.getSum(b)で[0,b)
"
• なので最終的に bit.getSum(b)-bit.getSum(a)

をすると[a,b)の和を求めることができる。
86
計算量
• xiにvを入れる       O(log n)
• [a,b)の総和を調べる     O(log n)
"
• 上はいつでも任意の順番で行える。
87
これらが使える問題
• AtCoder ARC #026 C-蛍光灯
• http://arc026.contest.atcoder.jp/tasks/arc026_3
• Segment Tree
"
• AtCoder ARC #014 D - grepマスター
• http://arc014.contest.atcoder.jp/tasks/arc014_4
• 2分探索
88
参考・引用資料
• プログラミングコンテストでのデータ構造

SlideShare 秋葉 拓哉 / (iwi)
• http://www.slideshare.net/iwiwi/ss-3578491
89
以上お疲れ様でした。
90

第22回アルゴリズム勉強会資料