re永続データ構造が分からない人のためのスライド

88
Re: 永続データ構造が分からな い人のためのスライド @qnighy Competitive Programing Advent Calendar 2012

Upload: masaki-hara

Post on 04-Jun-2015

4.304 views

Category:

Technology


3 download

DESCRIPTION

Competitive Programming Advent Calendar 2012の12/01担当分の記事です。

TRANSCRIPT

Page 1: Re永続データ構造が分からない人のためのスライド

Re: 永続データ構造が分からない人のためのスライド

@qnighy Competitive Programing Advent Calendar 2012

Page 2: Re永続データ構造が分からない人のためのスライド

このスライドについて

• Competitive Programming Advent Calendar 2012の12/1の担当記事です。

• Advent Calendarのサイトはこちら

Page 3: Re永続データ構造が分からない人のためのスライド

経緯

Page 4: Re永続データ構造が分からない人のためのスライド

経緯

Page 5: Re永続データ構造が分からない人のためのスライド

経緯

Page 6: Re永続データ構造が分からない人のためのスライド

経緯

Page 7: Re永続データ構造が分からない人のためのスライド

経緯

Page 8: Re永続データ構造が分からない人のためのスライド

経緯

• 被害者が何名か出ました

• 今日は少しだけ真面目に書きます

Page 9: Re永続データ構造が分からない人のためのスライド

問題

• 三問やります

– Crayfish Scrivener (IOI 2012, day 1)

– L-th Number (UTPC 2011)

– Copy and Paste (JOI Spring 2012, day 4)

• 三問における使い方の違いみたいなのに焦点を当てていこうかと思っています

• ネタバレあり • 僕より詳しい人のツッコミが入る可能性に怯えています

Page 10: Re永続データ構造が分からない人のためのスライド

永続データ構造

• 変更前のデータを扱えるデータ構造の総称

D1 D2 変更操作

普通のデータ構造

Page 11: Re永続データ構造が分からない人のためのスライド

永続データ構造

• 変更前のデータを扱えるデータ構造の総称

D1

D2

変更操作

永続データ構造

D1

Page 12: Re永続データ構造が分からない人のためのスライド

完全永続性

• 部分永続: 最新版のみを変更可能

D1 D2

D3 D4

Page 13: Re永続データ構造が分からない人のためのスライド

完全永続性

• 完全永続: 最新版のみを変更可能

• ここでは完全永続データ構造のみ扱う

D1

D2

D4 D5

D3 D6

D7

D8

D9

Page 14: Re永続データ構造が分からない人のためのスライド

2つの視点

• 履歴としての永続データ構造

• 値型としての永続データ構造

Page 15: Re永続データ構造が分からない人のためのスライド

履歴としての永続データ構造

• 変更履歴を遡れる、という視点で見る

–昔使ったデータを再利用したい、というイメージ

Page 16: Re永続データ構造が分からない人のためのスライド

値型としての永続データ構造

• 永続データ構造は元の値を保存する

–破壊的変更を隠せる

• 値型として見なせる

– 2つの値を演算して新しい値を作る場合も…

Page 17: Re永続データ構造が分からない人のためのスライド

問題

• Crayfish Scrivener (IOI 2012, day 1)

• L-th Number (UTPC 2011)

• Copy and Paste (JOI Spring 2012, day 4)

Page 18: Re永続データ構造が分からない人のためのスライド

問題

• Crayfish Scrivener (IOI 2012, day 1)

Page 19: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 問題文の入手先

• 概要

–エディタの動作をシミュレーションします

–入力操作: 末尾に1文字足します。

– undo操作: 直前のK個をもとに戻します

–入力・undoはそれぞれ1回の操作とみなします

• つまり、undoのundoができます

–文字取得: 現在のi番目の文字を教えて下さい

Page 20: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• undoのundo???

Page 21: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

0: (空)

1: A

Page 22: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

0: (空)

1: A

2: AB

Page 23: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

0: (空)

1: A

2: AB

3: A

Page 24: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

4. Cを入力

0: (空)

1: A

2: AB

3: A

4: AC

Page 25: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

4. Cを入力

5. 2回分もとに戻す

0: (空)

1: A

2: AB

3: A

4: AC 5: AB

Page 26: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

4. Cを入力

5. 2回分もとに戻す

6. Dを入力

0: (空)

1: A

2: AB

3: A

4: AC 5: AB

6: ABD

Page 27: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

4. Cを入力

5. 2回分もとに戻す

6. Dを入力

• これを”作業履歴 ツリー”と呼ぶ ことにする

0: (空)

1: A

2: AB

3: A

4: AC 5: AB

6: ABD

Page 28: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• undoのundo???

• →冷静に考えれば、単純にK個前の時の文字列をコピーしてくればよいことがわかります。

Page 29: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 入力操作(文字c)

1. S[t+1] ← S[t] + c

2. t++

• undo操作(K回)

1. S[t+1] ← S[t-K]

2. t++

• 読み出し操作

1. return S[t][i]

Page 30: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 素朴に実装すると二乗

• 文字列のコピーコストを減らしたい

Page 31: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 素朴に実装すると二乗

• 文字列のコピーコストを減らしたい

• 配列を永続化すれば良さそう

Page 32: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 素朴に実装すると二乗

• 文字列のコピーコストを減らしたい

• △ 配列を永続化すれば良さそう

Page 33: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 素朴に実装すると二乗

• 文字列のコピーコストを減らしたい

• △ 配列を永続化すれば良さそう

• ○ リストを永続化しよう!!

Page 34: Re永続データ構造が分からない人のためのスライド

リスト = 永続stack

• リストへの追加: new Node(a, item)

• リストからの削除: a->next

• 先頭アイテムの取得: a->item

Page 35: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

A

[0]

[1]

Page 36: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

A

[0]

[1]

B [2]

Page 37: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

A

[0]

[1] [3]

B [2]

Page 38: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

4. Cを入力

A

[0]

[1] [3]

B [2] C [4]

Page 39: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

4. Cを入力

5. 2回分もとに戻す

A

[0]

[1] [3]

B [2] [5] C [4]

Page 40: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例: 1. Aを入力

2. Bを入力

3. 1回分もとに戻す

4. Cを入力

5. 2回分もとに戻す

6. Dを入力

A

[0]

[1] [3]

B [2] [5] C [4]

D [6]

Page 41: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 永続リストを使って簡潔に表現できた!

• でもi番目の文字はどうやって見つけるのか?

• 次のページに答えを書きます。

Page 42: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 永続リストを使って簡潔に表現できた!

• でもi番目の文字はどうやって見つけるのか?

• 答え:

–普通のリストでは1文字前のリンクのみ保持している。

– 2^x文字前のリンクを全て持てば良い。

Page 43: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例に戻ってみる 空

A

[0]

[1] [3]

B [2] [5] C [4]

D [6]

Page 44: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例に戻ってみる

• 各時点でのデータは リストだった

A

B

D

Page 45: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例に戻ってみる

• 各時点でのデータは リストだった

A

C

Page 46: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例に戻ってみる

• 各時点でのデータは リストだった

• 全体を俯瞰すると 逆向きの木になっている ことがわかる

A

[0]

[1] [3]

B [2] [5] C [4]

D [6]

Page 47: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener

• 例に戻ってみる

• 各時点でのデータは リストだった

• 全体を俯瞰すると 逆向きの木になっている ことがわかる

• これを「通時的構造」とでも 呼ぶことにしよう

A

[0]

[1] [3]

B [2] [5] C [4]

D [6]

Page 48: Re永続データ構造が分からない人のためのスライド

Crayfish Scrivener (一般化)

• 一般にi文字目を書き換えるよう一般化した問題を考える

• →永続配列を実装する必要がある

• Wikipediaの「永続データ構造」の記事には永続配列としてVListが紹介さ

れているが、これは書き込みがランダムアクセスではないために配列とは呼べない。また、完全永続的でないので今回の問題には適さない。

Page 49: Re永続データ構造が分からない人のためのスライド

永続配列

• 配列: map<int,T>として見なせる

• 永続な平衡二分木を作れば良さそう

Page 50: Re永続データ構造が分からない人のためのスライド

永続配列

• 配列: map<int,T>として見なせる

• 永続な平衡二分木を作れば良さそう

• ただし、一般的な永続平衡二分木を実装する必要はない

–キーであるintのもつ性質を活用する

– これについては次のセクションで解説

Page 51: Re永続データ構造が分からない人のためのスライド

問題

• Crayfish Scrivener (IOI 2012, day 1)

• L-th Number (UTPC 2011)

• Copy and Paste (JOI Spring 2012, day 4)

Page 52: Re永続データ構造が分からない人のためのスライド

問題

• L-th Number (UTPC 2011)

Page 53: Re永続データ構造が分からない人のためのスライド

L-th Number

• 問題文の入手先

• 概要

–無向木があります。頂点に数字が書かれています。

– クエリ(v,w,L)が順番に与えられます。

–頂点vと頂点wを結ぶ経路上の数字のうち、L番目に小さいものを答えて下さい。

Page 54: Re永続データ構造が分からない人のためのスライド

L-th Number

• 解説PDFがあるので、ちゃんと解説を知りたい人はそっちを読んでください。

• 一応、次のページで軽く解説します。

Page 55: Re永続データ構造が分からない人のためのスライド

L-th Number

• 無向木に根を与えて有向木にします。

• 頂点vから根までの経路上での数字の分布を木構造で管理します。(総和を計算するRMQを作ります。)

• 頂点vとwの間での分布は、その差分で得られます。

• 二分探索で答えが出ます。

Page 56: Re永続データ構造が分からない人のためのスライド

L-th Number

• 無向木に根を与えて有向木にします。

• 頂点vから根までの経路上での数字の分布を木構造で管理します。(総和を計算するRMQを作ります。)

• 頂点vとwの間での分布は、その差分で得られます。

• 二分探索で答えが出ます。

Page 57: Re永続データ構造が分からない人のためのスライド

L-th Number

• 「頂点vから根における分布」→「その子供wから根における分布」の計算

–分布を表すデータ構造に変更を加える

–でも元のデータも残したい、という問題

– →「履歴としての永続データ構造」と見なせる

Page 58: Re永続データ構造が分からない人のためのスライド

L-th Number

• 作業履歴ツリーは例えば以下のようになる

• (公式解説スライドの例と同じもの)

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

Page 59: Re永続データ構造が分からない人のためのスライド

L-th Number

• 永続リストの通時的構造は木だった

• 永続木の通時的構造は?

Page 60: Re永続データ構造が分からない人のためのスライド

L-th Number

• 先ほどの例を使ってやってみよう

• (平衡のさせかたは適当)

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

Page 61: Re永続データ構造が分からない人のためのスライド

L-th Number

• 頂点1

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

2

Page 62: Re永続データ構造が分からない人のためのスライド

L-th Number

• 頂点3

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

2

5

Page 63: Re永続データ構造が分からない人のためのスライド

L-th Number

• 頂点2

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

2

4

5

Page 64: Re永続データ構造が分からない人のためのスライド

L-th Number

• 頂点4

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

2

5

8

Page 65: Re永続データ構造が分からない人のためのスライド

L-th Number

• 頂点5

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

2

5

8

9

Page 66: Re永続データ構造が分からない人のためのスライド

L-th Number

• 頂点6

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

2

5

7

8

Page 67: Re永続データ構造が分からない人のためのスライド

L-th Number

• 通時的構造

2 空

2,5

2,4,5 2,5,8

2,5,8,9 2,5,7,8

2

4

5

5

7 8

9

8

8

Page 68: Re永続データ構造が分からない人のためのスライド

L-th Number

• DAGになっていることが おわかり頂けただろうか

• (この例だと特殊なのでわかりにくいが…)

Page 69: Re永続データ構造が分からない人のためのスライド

実装上のテクニック

• 実装上のテクニック

– Crayfish Scrivenerと併せて

• キーがintであることなどに注目すると、赤黒とかは必要ないことがわかる。

–木の形状は固定でよい

–データは葉にのみ持たせたほうが楽ちん

• ただし、L-th NumberはそもそもSegment Treeなので、生データを葉にのみ持たせるのは割と当然

Page 70: Re永続データ構造が分からない人のためのスライド

実装上のテクニック

• L-th Numberの場合

• 「存在する数字の集合」ではなく、「ある範囲内に収まる数字はいくつあるか」で持っておく

• 木の形状を固定にすると良い点

–実装が簡単

–オーダーが落ちる(二分探索との併用なので、計算しつつ探索を下ることができれば速くなる)

Page 71: Re永続データ構造が分からない人のためのスライド

実装上のテクニック

• 参考問題

– Apples (JOI Spring 2011 day4)

• 実は木の形状を固定できる例

• 遅延評価でメモリを節約する工夫が必要

– Bug Party (JOI Final 2011)

• 二分探索とツリー処理の合併で高速化できる例

Page 72: Re永続データ構造が分からない人のためのスライド

ここまでの地図

• 履歴としての永続データ構造

–作業履歴が定義できて、それが木構造をなす

– リストの例: Crayfish Scrivener

• 通時的構造が木になることがわかった

–木構造の例: L-th Number

• 通時的構造がある種のDAGになることがわかった

• 値型としての永続データ構造

–??

Page 73: Re永続データ構造が分からない人のためのスライド

問題

• Crayfish Scrivener (IOI 2012, day 1)

• L-th Number (UTPC 2011)

• Copy and Paste (JOI Spring 2012, day 4)

Page 74: Re永続データ構造が分からない人のためのスライド

問題

• Copy and Paste (JOI Spring 2012, day 4)

Page 75: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• 問題文の入手先

• 概要

–エディタの動作をシミュレーションします

–初期入力: 初期文字列Sが与えられます。

– コピペ: ある範囲をある位置にコピー・挿入します

–溢れ: M文字より溢れたらトリミングします

–最終結果を出力してください

Page 76: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• これも、よくわかる解説スライドがあるので、もういいんじゃないかと思いますが………

• 一応、今回のテーマに関係する部分を適当に解説します

Page 77: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• 見た感じでヤバい問題 – これを見た感じでヤバいって思えないと大きな失態を演じることになるから気をつけた方がいい

• なんかこう、副作用を気にしないで好きなように切り貼りできる便利なのが欲しいよね

Page 78: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• 副作用を気にしないで?

–永続データ構造?

• 切ったり貼ったり出来る?

–平衡二分探索木?

• !!永続な平衡二分探索木を組もう!!

Page 79: Re永続データ構造が分からない人のためのスライド

永続やるときの制約

• ならし解析はだいたい無理

–完全永続的なデータ構造では、同じ状態遷移を何回も繰り返せるので、ならし解析が壊れる

• 乱択アルゴリズムも難しいらしい

– こっちはよくわからないけどとにかく難しいらしい

Page 80: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• !!赤黒木を書こう!!

Page 81: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• 実装上のテクニックは公式解説にあるので省略 – 実は自分で実装してみていないから迂闊なこと言えないんだ、なんて言えない…

Page 82: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• この問題では、次のような操作があることに注意

–文字列の連結(c := a + b)

• 今までの例では、1つのデータを少しだけ加工して新しいデータを作っていた

• ここでは、2つのデータを併せて何かをする操作が出てくる

• =値型としての永続データ構造

Page 83: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• 「値型としての永続データ構造」の性質

–通時的構造はDAGになるが、先程のものとは違う性質を持っている

–それは、「頂点vからwへの遷移方法が2通り以上ある可能性がある」ということ

–そういう性質などもあって、こういう永続データ構造は扱いがめんどいようです。

Page 84: Re永続データ構造が分からない人のためのスライド

Copy and Paste

• 本当はこれの実装上のテクニックとかも書く予定でしたが、もう眠いので止めます。

• ゴミ回収の必要とかがあるんですが、C++使いの人はshared_ptrがあるから

–はやく競プロでBoostが使えるようになるといいですね………………

Page 85: Re永続データ構造が分からない人のためのスライド

突飛なまとめ

• 永続データ構造と一口にいってもその条件は何段階かある – 特に、insert/delete系の操作しか行わない場合と、mergeが必要な場面では実装の要求レベルがだいぶ変わってくる

• 永続データ構造を、通時的観点から俯瞰すると新たな視点が得られる(かも)

• つまり、「代数的データ型でデータ構造を構築する」という観点から永続データ構造を理解することも重要だが、結局は競技プログラミングにおける手法の一つとして使うのだから、手続き型的な観点から解体して、必要なテクニックだけ取り出せるようにすると良いよね

Page 86: Re永続データ構造が分からない人のためのスライド

参考

• 著名データ構造の永続的な実装の例

• 配列 : 二分木による実装

• スタック : リストと等価

• ヒープ : Leftist Heapの永続的実装 – Binomial Heapの永続的実装もある

• キュー : Finger Tree, Implicit Queue

• マップ : 赤黒木系統、AVL系統

• DSU(Union Find) : Sylvain Conchon and Jean-Christophe Filliâtreによる論文

Page 87: Re永続データ構造が分からない人のためのスライド

参考文献

• Chris Okasaki, 1999, Purely Functional Data Structures, Cambridge University Press, 232 Pages.

• Sylvain Conchon and Jean-Christophe Filliâtre, 2007, A persistent union-find data structure, ML ‘07 Proceedings of the 2007 workshop on ML, Pages 37-46, ACM.

• VList – Wikipedia, the free encyclopedia (2012/12/02閲覧) • 素集合データ構造 – Wikipedia (2012/12/02閲覧) • 永続データ構造 – Wikipedia (2012/12/02閲覧) • 2-3 フィンガーツリー – Wikipedia (2012/12/02閲覧) • Tasks | IOI 2012 (2012/12/02閲覧) • 東京大学プログラミングコンテスト2011 (2012/12/02閲覧) • 問題一覧 – 情報オリンピック 問題と解説 (2012/12/02閲覧)

Page 88: Re永続データ構造が分からない人のためのスライド

fin

• ご清聴ありがとうございました