プログラミングコンテストでのデータ構造 2 ~動的木編~

30
プログラミングコンテストでの データ構造~動的木編~ 東京大学情報理工学系研究科 秋葉 拓哉 2012/3/20 NTTデータ駒場研修所 (情報オリンピック春合宿) 1

Upload: takuya-akiba

Post on 26-Jun-2015

17.837 views

Category:

Technology


2 download

DESCRIPTION

前編 (平衡二分探索木編) はこちら http://www.slideshare.net/iwiwi/2-12188757

TRANSCRIPT

Page 1: プログラミングコンテストでのデータ構造 2 ~動的木編~

プログラミングコンテストでの

データ構造2 ~動的木編~

東京大学情報理工学系研究科

秋葉 拓哉

2012/3/20 NTTデータ駒場研修所 (情報オリンピック春合宿)

1

Page 2: プログラミングコンテストでのデータ構造 2 ~動的木編~

動的木

• 実は動的木にもいくつかある

– 「順位キュー」的な言葉 (⇔ 二分ヒープ, フィボナッチヒープ)

• ポピュラーな動的木

– Link-Cut 木

– Euler-Tour 木 (動的グラフで内部的に使う)

• 今日は Link-Cut 木 の話をします

2

Page 3: プログラミングコンテストでのデータ構造 2 ~動的木編~

Link-Cut 木

• 木を処理する神がかり的なデータ構造!!

(木で木を処理するのでややこしい…)

• 以下が 𝑂(log 𝑛) 時間でできる.

– 頂点の親を変更 (link) / 削除 (cut)

– 木の根を求める (root) / 木の根を変更 (evert)

– パスに対する頂点・枝の値のクエリ

(sum・max・更新など)

3

Page 4: プログラミングコンテストでのデータ構造 2 ~動的木編~

Link-Cut 木は現実

Fan Haoqiang 氏は IOI’11 の Elephants で Link-Cut 木を実装!

• これに関して特別賞を受賞

• 599 点で準優勝

(´・_・`) そんなのどうせ実装できっこない…

IOI は Link-Cut 木で常勝!!

Fan Haoqiang 氏

(中国)

※発言はフィクションです

4

Page 5: プログラミングコンテストでのデータ構造 2 ~動的木編~

…でもやっぱその前に!

• 動的木は本当に必要?いつものじゃダメ?

• やはり,実装が面倒,避けられたら避けたい

• 実際のとこ,本当に必要になる問題は皆無

(特別賞を狙いたいのであればこの限りではない)

5

Page 6: プログラミングコンテストでのデータ構造 2 ~動的木編~

木上の距離クエリ

• 2 頂点間の枝を作成・削除してください

• 2 頂点間の距離を答えてください

ただしグラフは常に森.

(´・_・`) ツリーを処理するデータ構造なんて他に知らない…

動的木じゃないとできっこないよ…

( ・`д・´) 平方分割でもできるよ (重要!!)

6

Page 7: プログラミングコンテストでのデータ構造 2 ~動的木編~

クエリを平方分割

クエリ

終わった

部分

今やる

部分

• クエリを 𝐵 個ごとのブロックに分割

• 各ブロック内で操作に関わる頂点は

高々2𝐵個

• それ以外の頂点は興味が無いので,

ブロックを処理する最初に,縮約

– 𝑂(𝐵) 頂点の森になる

7

Page 8: プログラミングコンテストでのデータ構造 2 ~動的木編~

クエリを平方分割

クエリ

𝑂(𝐵)

𝑂(𝐵)

𝑂(𝐵)

• すると各クエリは 𝑂(𝐵) で処理可

– 普通に 𝑂(𝐵) 頂点の木を探索するだけ

𝑂𝑄

𝐵𝑁 + 𝑄𝐵 = 𝑂 𝑄 𝑁 時間

ただし,クエリが先読みできないと無理

縮約

𝑂(𝑁)

縮約

𝑂(𝑁) ←

縮約

𝑂(𝑁) ←

↑ 𝐵 = 𝑁 とした

類似した問題と,より詳しい解説: http://acm-icpc.aitea.net/index.php?plugin=attach&refer=2010%2FPractice%2F%B2%C6%B9%E7%BD%C9%2F%B9%D6%C9%BE&openfile=2d.pdf

8

Page 9: プログラミングコンテストでのデータ構造 2 ~動的木編~

レベル別 Link-Cut 木

• オプションは独立に選んで実装可

• 「頂点質問」 = ある頂点から根までのパスにおける頂点の属

性の sum とか max とかのこと

• 「頂点更新」 =とはパス上の頂点全部に x 足すとかのこと

expose (実装そこそこ)

link, cut, 頂点質問 (実装一瞬)

evert, 頂点更新 (実装少し)

辺質問・更新 (実装そこそこ)

ベース

オプション

9

Page 10: プログラミングコンテストでのデータ構造 2 ~動的木編~

基本アイディア

• ツリーをパスの集合みたいに表現

• パスを平衡二分探索木で管理

10

Page 11: プログラミングコンテストでのデータ構造 2 ~動的木編~

基本アイディア

パスへの分解は決まってない + 固定じゃない

こうなってるかもしれないし こうなってるかも

11

Page 12: プログラミングコンテストでのデータ構造 2 ~動的木編~

核となる操作 expose(𝒗)

頂点 v から根へのパスを繋げる

v v

切れる

←切れる

平衡二分探索木の split / merge を使う

12

Page 13: プログラミングコンテストでのデータ構造 2 ~動的木編~

link, cut の雰囲気

• cut(𝑣): 𝑣 から親への辺を削除

• link(𝑣, 𝑤): 𝑣 の親を 𝑤 にする

平衡二分探索木を切ったり繋げたりするだけ

w

v

w

v

cut

link

13

Page 14: プログラミングコンテストでのデータ構造 2 ~動的木編~

頂点クエリ

• sumv(𝑣): 頂点 v から根までの頂点たちに書いて

ある数の和

(あるいは minv, maxv, …)

やり方

• 平衡二分探索木に,部分木の和を持たせる

• expose(𝑣) して,和を見るだけ

14

Page 15: プログラミングコンテストでのデータ構造 2 ~動的木編~

各操作の計算量

結論: スプレー木を用いるとならし 𝑂 log 𝑛 時間

𝑂 log2 𝑛 時間の略証:

• expose の計算量だけ考えれば良い (他は余裕)

• 平衡二分探索木で管理されるパスに入る・出る枝の本数

をならし 𝑂 log 𝑛 本に抑えられれば良い

v v

1 本 “入って”

2 本 “出た”

15

Page 16: プログラミングコンテストでのデータ構造 2 ~動的木編~

• 出るためには入る必要がある,入る回数を抑えれば OK

• 元の木の Heavy-Light Decomposition を考える

• Light-Edge の本数を抑える

– 各頂点から根までの Light Edge はそもそも 𝑂 log𝑛 本

– よって,入る Light-Edgeは 𝑂 log𝑛 本

• Heavy-Edge の本数を抑える

– 1 度にいじる Heavy-Edge の本数は多いかも,でも,

– Heavy-Edge が入るということは Light-Edge が出る

– Light-Edge が出るためには Light-Edge が入ってるはず

– それはさっき数えた! 各クエリ 𝑂 log 𝑛 本!

– よってならし 𝑂 log 𝑛 本になる

• よって 𝑂 log 𝑛 ならし本.よって 𝑂 log2 𝑛 ならし時間.

16

Page 17: プログラミングコンテストでのデータ構造 2 ~動的木編~

各操作の計算量

𝑂 log 𝑛 時間:

• スプレー木のポテンシャルに踏み入って解析する

• 今日は省略

Link-Cut 木を実装していこう!

(気合い!)

(といってもスプレー木が出来れば殆ど終わり)

17

Page 18: プログラミングコンテストでのデータ構造 2 ~動的木編~

実装:ノードの構造体

Is_root は何故 !pp だけじゃない?

→ pp をパスの親を表すのにも使うため (後述)

(こうすると実装が凄い楽になります)

親でありながら子でないことがある

struct node_t { node_t *pp, *lp, *rp; // 親,左の子,右の子

// このノードは木の根?

bool is_root() { return !pp || (pp->lp != this && pp->rp != this); }

18

Page 19: プログラミングコンテストでのデータ構造 2 ~動的木編~

実装:ノードの構造体 (続き)

スプレー操作を Bottom-Up の方式で実装する (普通に平衡二分探索木でスプレー木を使うときは Top-Down の方式の方が良いが,

今回はノードのポインタを知ってるところから splay したいので Bottom-Up)

(参考:Top-Down の実装例 http://www.prefield.com/algorithm/container/splay_tree.html)

void rotr() { // 右回転

node_t *q = pp, *r = q->pp;

if ((q->lp = rp)) rp->pp = q;

rp = q; q->pp = this;

if ((pp = r)) {

if (r->lp == q) r->lp = this;

if (r->rp == q) r->rp = this;

}

}

void rotl() { // 左回転

node_t *q = pp, *r = q->pp;

if ((q->rp = lp)) lp->pp = q;

lp = q; q->pp = this;

if ((pp = r)) {

if (r->lp == q) r->lp = this;

if (r->rp == q) r->rp = this;

}

}

void splay() { // スプレー操作

while (!is_root()) {

node_t *q = pp;

if (q->is_root()) {

if (q->lp == this) rotr();

else rotl();

} else {

node_t *r = q->pp;

if (r->lp == q) {

if (q->lp == this) { q->rotr(); rotr(); }

else { rotl(); rotr(); }

} else {

if (q->rp == this) { q->rotl(); rotl(); }

else { rotr(); rotl(); }

}

}

}

}

19

Page 20: プログラミングコンテストでのデータ構造 2 ~動的木編~

実装:パスの親

• pp (親へのポインタ) を 2 通りの使い方をすると楽

– 普通に二分探索木内での親へのポインタ(緑・青の両向き)

– パスからの親へのポインタ (赤色の片方向)

1

2

a 3

4 b

5 c

3

2 5

1 4

a

c

b

表現の例

NULL

20

Page 21: プログラミングコンテストでのデータ構造 2 ~動的木編~

1

2

a 3

4 b 5 c

3

2

5 1

4

a

c

b

NULL

3

2

5 1

4

a

c

b

NULL

expose(c) 1

2

a 3

4 b

5 c

こうなってたら ノード 2 の右の子を

3 から a にするだけ!

21

Page 22: プログラミングコンテストでのデータ構造 2 ~動的木編~

実装:expose node_t *expose(node_t *x) {

node_t *rp = NULL;

for (node_t *p = x; p; p = p->pp) {

p->splay();

p->rp = rp;

rp = p;

} x->splay(); // しとくと便利

return x;

}

やること:

1. 今いる頂点を splay

2. 右側にさっきまで居た木を

くっつける

3. 上の木に進む

3

2 5

1 4

a

c

b

3

2 5

1 4

a c

b

Splay(c)

3 2

5 1

4

a c

b

2 に移動,splay(2)

2

1 a

c

b

3 5

4

2 の右に c をつける さいしょ

22

Page 23: プログラミングコンテストでのデータ構造 2 ~動的木編~

実装:link, cut

void cut(node_t *c) {

expose(c);

node_t *p = c->lp;

c->lp = NULL;

p->pp = NULL;

}

void link(node_t *c, node_t *p) {

expose(c);

expose(p);

c->pp = p;

p->rp = c;

}

cut

• c を expose

• c の左を切断

link

cut

p

c

p

c

link

• c, p を expose

• p の右に c を

つける

expose まで出来ていればもう簡単

23

Page 24: プログラミングコンテストでのデータ構造 2 ~動的木編~

実装:evert

evert(𝑣):𝑣 を根にする

木のパスの向きを反転すればよい

void evert(node_t *p) {

node_t *r = expose(p);

r->rev = true;

} v

v

+ スプレー木に

反転の機能を実装

24

Page 25: プログラミングコンテストでのデータ構造 2 ~動的木編~

実装:辺属性

• 木の辺に情報をつける

• 回転とか張替えでポインタと一緒に情報を保つ (注意深く)

• 部分木に関する情報も保つ

1

2

a 3

4 b

5 c

3

2 5

1 4

a

c

b

NULL

1

2

3

4

1

2 3

4

5

6

7

5

6

7

25

Page 26: プログラミングコンテストでのデータ構造 2 ~動的木編~

応用:Elephants (IOI’11)

• 象が 𝑁 匹並んでます

• クエリが大量にくる

– Update(𝑖, 𝑥)

– i 匹目の象を場所 x に移動

• クエリの度に答える

– 何台のカメラで全員写る?

– カメラ:幅 𝐿

26

Page 27: プログラミングコンテストでのデータ構造 2 ~動的木編~

応用:Elephants (IOI’11)

• 愚直な解法:クエリ毎に計算する

– 貪欲法の典型的問題

– 一番左の象を覆う,を繰り返せば良い

• 動的木を使う解法:

– 似たような感じのことを木で表現する

– 移動は木のちょっとした更新になる

– よってクエリに爆速で答えられる

27

Page 28: プログラミングコンテストでのデータ構造 2 ~動的木編~

応用:Elephants (IOI’11)

• 象の場所に ● を書く

• そこから L 先に ○ を書き,●から辺を張る

• ○からはすぐ次の●か○に辺を張る

• これはツリー!

• 一番左から辿って,●の個数が答え!

28

Page 29: プログラミングコンテストでのデータ構造 2 ~動的木編~

Link-Cut 木まとめ

• まずはもっと容易な道具を検討!

– クエリの平方分割

• 実装しよう (下に行くほど大変)

– expose, link, cut, root, 頂点の情報に関する質問

– evert, 頂点の情報の更新

– 辺の情報に関する質問・更新

29

Page 30: プログラミングコンテストでのデータ構造 2 ~動的木編~

全体まとめと私見

話したこと

• 平衡二分探索木

– 本当に必要か検討,必要な場所だけ作るセグメント木

– Treapのアイディア,楽な実装法の議論,応用例

– その他の平衡二分探索木の紹介

• 動的木

– 本当に必要か検討,クエリの平方分割

– Link-Cut Tree のアイディア,楽な実装法の議論,応用例

私見

• これらは難易度が高く,想定解法としての出題頻度は低い

• よって,習得の優先度は高くない

• ただし,非常に強力な道具なので,武器にできれば得をするかも?

30