SlideShare uma empresa Scribd logo
1 de 51
Baixar para ler offline
2012/3/20 NTTデータ駒場研修所 (情報オリンピック春合宿)




   プログラミングコンテストでの
             データ構造2
          ~平衡二分探索木編~

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

                   秋葉 拓哉
                                      1
自己紹介
• 秋葉 拓哉 / @iwiwi


• 東京大学 情報理工学系研究科 コンピュータ科学専攻


• プログラミングコンテスト好き


• プログラミングコンテストチャレンジブック

                              2
データ構造たち
           (もちろん他にもありますが)

• 二分ヒープ
• 組み込み辞書 (std::map)
• Union-Find 木                     初級編

• Binary Indexed Tree   コンテストでの
                        データ構造1
• セグメント木                (2010 年)

• バケット法
                                   中級編
• 平衡二分探索木
• 動的木                   本講義
• (永続データ構造)

                                         3
話すこと
1. 平衡二分探索木
2. 動的木
3. 永続データ構造 (時間あれば)


• ちょっとむずい! 「へ~」ぐらいの気持ちでも OK!
• アイディアだけじゃなくて,実装にまで立ち入り,簡潔
  に実装するテクを伝授したい



                               4
平衡二分探索木


          5
普通の二分探索木
                7




    2                             15




1           5           10                  17




        4           8        11        16        19

                                                      6
普通の二分探索木: 検索 (find)
10 を検索           7        7と比較



                                           15と比較

     2                               15

                     発見

 1           5             10                  17



         4            8         11        16        19

                                                         7
普通の二分探索木: 挿入 (insert)
           7と比較         7                 6 を挿入


2と比較
       2                                  15


                   5と比較

  1            5                10                  17



           4       6        8        11        16        19

                   追加
                                                              8
普通の二分探索木: 削除 (erase)
15 を削除               7
                                       15    削除




         2                             11




  1              5           10                  17




             4           8        11        16        19

                                                           9
普通の二分探索木の偏り
      1, 2, 3, 4, … の順に挿入すると…?
  1
       2               高さが 𝑂 𝑛 !
           3
                       処理に 𝑂 𝑛 時間!
               4
                   5   やばすぎ!

こういった意地悪に耐える工夫をする二分探索木
     = 平衡二分探索木が必要!
                                     10
…その前に!
      平衡二分探索木は本当に必要?

• いつものじゃダメ?
 – 配列,線形リスト
 – std::set, std::map
 – Binary Indexed Tree,バケット法,セグメント木


• 実装が面倒なので楽に避けられたら避けたい
• 実際のとこ,本当に必要になる問題はレア

                                      11
例
      範囲の大きな Range Minimum Query
    • ある区間の最小値を答えてください
    • ある場所に数値を書いてください
    ただし場所は 1~109.デフォルト値は ∞ .


               そんな大きな配列作れない…
               セグメント木じゃできない…
(´・_・`)

               セグメント木でもできるよ
( ・`д・´)    クエリ先読みして座標圧縮しとけばいいよ



                                   12
例
      範囲の大きな Range Minimum Query
    • ある区間の最小値を答えてください
    • ある場所に数値を書いてください
    ただし場所は 1~109.デフォルト値は ∞ .


               でもクエリ先読みできないかも…
           (情オリの interactive とか,計算したら出てくるとか)
(´・_・`)

            必要な場所だけ作るセグメント木でいいよ
( ・`д・´)

                                               13
必要な場所だけ作るセグメント木

                                     2

                     3                               2

             3               ∞               8               2

         5       3                       ∞       8       2       ∞


         5       3       ∞       ∞       ∞       8       2       ∞


• 以下が全てデフォルト値になってるノードは要らない
•   𝑂(クエリ数 𝐥𝐨𝐠(場所の範囲)) のノードしかできない
•   𝑂(𝐥𝐨𝐠 場所の範囲 ) でクエリを処理できる

春季選考合宿 2011 Day4 Apple 参照
                                                                     14
例2
       反転のある Range Minimum Query
    • ある区間の最小値を答えてください
    • ある区間を左右反転してください


                 反転なんてできない…
(´・_・`)


( ・`д・´)           ・・・

    おとなしく平衡二分探索木を書こう!
                                   15
平衡二分探索木
                                     (&仲間)

                        超いっぱいあります
                    AVL 木,赤黒木,AA 木,2-3 木,2-3-4 木,スプレー木,
  Scapegoat 木,Treap,Randomized Binary Search Tree,Tango 木,Block Linked List,Skip List,…



• ガチ勢: 赤黒木 (std::map とか)
 – (定数倍的な意味で) かなり高速
 – でも実装が少し面倒


• コンテスト勢: 実装が楽なのを組もう!


                                                                                          16
コンテストでの平衡二分探索木
• 大抵,列を管理するために使われる
 (探索木という感じより,ただの順序を持った列を高機能に扱う感じが多い)



• よく必要になるもの
 – 𝑘 番目に挿入 (insert),𝑘 番目を削除 (erase)
 – 0, 𝑘 と 𝑘, 𝑛 の 2 つの列に分割 (split)
 – 2 つの列を連結 (merge)
 – 値に関する質問・更新 (sum, add 等)


         これをサポートする木を作ろう!

                                       17
Treap の思想
• 根がランダムに選ばれるようにする!
 – 全ノードが等確率で根になるようにする
 – 部分木に関しても再帰的に,根をランダムに選ぶこ
   とを繰り返す


• これだけで高さが 𝑂 log 𝑛

• なぜ? 乱択クイックソートと同じ.


                             18
Treap の思想
  Treap / RBST    乱択クイックソート

   ランダムに選ばれた
                    ランダムに選ばれた
       根
                      ピボット
 根より   ↓  根より
                        ↓
小さい値     大きい値

                    ↑        ↑
                 ピボットより   ピボットより
                  小さい値     大きい値




                                   19
Treap の思想 (別解釈)
    普通だと,挿入順は木にどう影響する?

c            c       c               c
         b       b       d       b       d
                             a


    ナイーブな二分探索木に c → b → d → a と挿入

先に挿入したものが上,後に挿入したものが下

                                             20
Treap の思想 (別解釈)
• 普通の二分探索木でも,もしランダム順に挿入
  されてたら必ず高さ 𝑂 log 𝑛


• 実際の挿入順に構わず,ランダム順に挿入され
  たかのように扱おう!
 – 常に std::random_shuffle した後で挿入されたかのう




                                     21
Treap
• 乱数を用いる平衡二分探索木
• 各ノードは,キーの他,優先度を持つ
 – 優先度は挿入時にランダムで決める




                      キー


                      優先度
                            22
Treap
以下の 2 つの条件を常に保つ
1. キーを見ると二分探索木
2. 優先度を見ると二分ヒープ
(Tree + Heap なので Treap という名前らしい…ワロスwww)


                                     大

                                    優
                                    先
                                    度

                                     小


                  小    キー      大
                                          23
Treap
• 優先度が最高のやつが根になる
 – これは,全ノード等確率! → 平衡!
 – 部分木に関しても再帰的に同様


                          大

                          優
                          先
                          度

                          小


          小    キー     大
                              24
Treap の実装法
     大まかに 2 つの方法があります

           insert-erase ベース
(insert, erase を実装し,それらの組み合わせで merge, split)




           merge-split ベース
(merge, split を実装し,それらの組み合わせで insert, erase)




                                               25
Treap 実装: ノード構造体
struct node_t {
 int val;          // 値
 node_t *ch[2];    // = {左, 右};
 int pri;         // 優先度
 int cnt;         // 部分木のサイズ
 int sum;         // 部分木の値の和

  node_t(int v, double p) : val(v), pri(p), cnt(1), sum(v) {
    ch[0] = ch[1] = NULL;
  }
};




                                                               26
Treap 実装: update
int count(node_t *t) { return !t ? 0 : t->cnt; }
int sum(node_t *t) { return !t ? 0 : t->sum; }

node_t *update(node_t *t) {
 t->cnt = count(t->ch[0]) + count(t->ch[1]) + 1;
 t->sum = sum(t->ch[0]) + sum(t->ch[1]) + t->val;
 return t; // 便利なので t 返しとく
}


               部分木に関する情報を計算しなおす
           子が変わった時などに必ず呼ぶようにする

                                                    27
Treap 実装: insert
  (insert-erase ベース)




まず優先度を無視して普通に挿入


                       28
Treap 実装: insert
     (insert-erase ベース)




優先度の条件が満たされるまで回転して上へ


                          29
回転




左右の順序を保ちつつ親子関係を変える
 上図のようにポインタを貼り直す

                     30
Treap 実装: 回転
                       (insert-erase ベース)
node_t *rotate(node_t *t, int b) {
  node_t *s = t->ch[1 - b];
  t->ch[1 - b] = s->ch[b];
  s->ch[b] = t;
  update(t); update(s);
  return s;
}


                 子を,別の変数でなく,配列にすると,
                     左右の回転が 1 つの関数でできる

             親の親のポインタを張り替えなくて良いのは,
       各操作が常に部分木の根を返すように実装してるから (次)
                                            31
Treap 実装: insert
                   (insert-erase ベース)
// t が根となっている木の k 番目に 値 val,優先度 pri のノード挿入
// 根のノードを返す
node_t *insert(node_t *t, int k, int val, double pri) {
  if (!t) return new node_t(val, pri);
  int c = count(t->ch[0]), b = (k > c);
  t->ch[b] = insert(t->ch[b], k - (b ? (c + 1) : 0), val, pri);
  update(t);
  if (t->pri > t->ch[b]->pri) t = rotate(t, 1 - b);
}


 このように,新しい親のポインタを返す実装にすると楽
                (親はたまに変わるので.)

                                                                  32
Treap 実装: erase
        (insert-erase ベース)

1. 削除したいノードを葉まで持っていく
 – 削除したいノードの優先度を最低にする感じ
 – 回転を繰り返す


2. そしたら消すだけ




                             33
Treap 実装: merge / split
                 (insert-erase ベース)

• insert, erase が出来たら merge, split は超簡単

• merge(𝑙, 𝑟)
   – 優先度最強のノード 𝑝 を作る
   – 𝑝 の左の子を 𝑙,右の子を 𝑟 にする
   – 𝑝 を erase

• split(𝑡, 𝑘)
   – 優先度最強のノード 𝑝 を木 𝑡 の 𝑘 番目に挿入
   – 𝑝 の左の子と右の子をそっと取り出す

                                          34
Treap 実装: insert / erase
                 (merge-split ベース)
• 逆に,merge, split が出来たら insert, erase は超簡単

• insert(木 t, 場所 k, 値 v)
   – 木 t を場所 k で split
   – 左の部分木,値 v のノードだけの木,右の部分木を merge

• erase(木 t, 場所 k)
   – 木 t を場所 k - 1 と場所 k で 3 つに split (split 2 回やればいい)
   – 一番左と一番右の部分木を merge


   今度は, merge, split を直接実装してみよう

                                                         35
Treap 実装: merge
                      (merge-split ベース)

                                       a
      a                b

              +                =   A
                                                       b

  A       B       C        D
                                           B
                                               +
                                                   C       D




• 優先度の高い方の根を新しい根にする
• 再帰的に merge

                                                               36
Treap 実装: merge
                     (merge-split ベース)
node_t *merge(node_t *l, node_t *r) {
 if (!l || !r) return !l ? r : l;

    if (l->pri > r->pri) {     // 左の部分木の根のほうが優先度が高い場合
      l->rch = merge(l->rch, r);
      return update(l);
    } else {                   // 右の部分木の根のほうが優先度が高い場合
      r->lch = merge(l, r->lch);
      return update(r);
    }
}


※ merge-split ベースだと,子を ch[2] みたいに配列で管理するメリットは薄い
               上では代わりに lch, rch としてしまっている
                                                   37
Treap 実装: split
                           (merge-split ベース)

      split は優先度の事を何も考えないで再帰的に切るだけ
            (部分木内の任意のノードは根より優先度小なので大丈夫)

pair<node_t*, node_t*> split(node_t *t, int k) { // [0, k), [k, n)
 if (!t) return make_pair(NULL, NULL);

    if (k <= count(t->lch)) {
      pair<node_t*, node_t*> s = split(t->lch, k);
      t->lch = s.second;
      return make_pair(s.first, update(t));
    } else {
      pair<node_t*, node_t*> s = split(t->rch, k - count(t->lch) - 1);
      t->rch = s.first;
      return make_pair(update(t), s.second);
    }
}

                                                                         38
Treap 実装法の比較
                insert-erase ベース
     (insert, erase を実装し,それらの組み合わせで merge, split)


                merge-split ベース
     (merge, split を実装し,それらの組み合わせで insert, erase)


• どっちでも良いです,好きな方で
• ただ,個人的には,merge-split ベースのほうが遥かに楽!!
  – 回転が必要ない,を筆頭に,頭を使わなくて済む
  – コードも少し短い
  – あと,コンテストでは,insert, erase より merge, split が必要にな
    ることの方が多い


                                                    39
その他,実装について
• malloc・new
  – 解放・メモリリークやオーバーヘッドが気になる?
  – グローバル変数としてノードの配列を 1 つ作っておき,そこか
    ら 1 つずつ取って使うと良い


• merge-split ベースでの真面目な insert
  1. 優先度が insert 先の木より低ければ再帰的に insert
  2. そうでなければ,insert 先の木を split してそいつらを子に
  – という真面目な実装をすると,定数倍すこし高速
  – erase も同様:再帰していって merge

                                           40
例題
       反転のある Range Minimum Query
    • ある区間の最小値を答えてください
    • ある区間を左右反転してください


           …結局これはどうやるの? 反転って?
(´・_・`)

                2 つの方法があるよ
( ・`д・´)


                                   41
反転: 方法 1
         真面目に反転を処理する
struct node_t {
  int val;        // 値
  node_t *ch[2]; // = {左, 右};
  int pri;       // 優先度
  int cnt;       // 部分木のサイズ
  int min;      // 部分木の値の最小 (RMQ のため)
  bool rev;     // 部分木が反転していることを表すフラグ
  …
};

       まずは構造体にフィールドを追加
                                        42
反転: 方法 1
区間 [l, r) を反転したいとする


1. 場所 l, r で split → 3 つの木を a, b, c とする
2. b の根ノードの rev フラグをトグル
3. 木 a, b, c を merge


         rev フラグはどのように扱う?

                                          43
反転: 方法 1
void push(node_t *t) {
  if (t->rev) {
• rev フラグの扱い
    swap(t->lch, t->rch);
    if (t->lch) t->lch->rev ^= true; // 子に反転を伝搬
    if (t->rch) t->rch->rev ^= true; // 子に反転を伝搬
    t->rev = false;
  }
}

   rev フラグによる反転を実際に反映する関数 push
           ノードにアクセスするたびに最初にこれを呼ぶようにする
            セグメント木における更新遅延と同様のテクニック
             (いっぱい push 書きます,書き忘れに注意!)


      ※数値の更新なども同様に,フラグ的変数作って push する
                           (区間への一様な加算など)

                                                  44
反転: 方法 2
はじめから 2 本の列をツリー t1, t2 で管理
• t1:順向き
• t2:逆向き

区間 [l, r) を反転したいとする
• t1 の [l, r) と,t2 の [l, r) を split して切り出す
• 交換して merge
                         t1   1   2   3   4   5   6

簡単.
(ただし,無理なケースも.)           t2   6   5   4   3   2   1


                                                      45
他色々: RBST
          (Randomized Binary Search Tree)
• Treap と同様に,ランダムなノードを根に来させる
• ただし,ノードに優先度など余分な情報が不要!

• merge(a, b)
   – n ノードの木 a と m ノードの木 b マージの場合
   – 全体 (n + m) ノードから根が等確率で選ばれていれば良い
           𝑛                      𝑚
   – 確率       で   a の根を新しい根,確率         で b の根を新しい根に
          𝑛+𝑚                    𝑛+𝑚
   – これを,必要に応じて乱数を発生して決める

Treap よりこっちのほうが構造体がシンプルになってカッコイイかも

                                                  46
他色々: スプレー木
• 一見よくわからん回転を繰り返す
• でも実はそれで平衡される!という不思議系データ構造


• 基本: ノード 𝑥 にアクセスする際,そのついでに回転を
  繰り返して 𝑥 を木の根まで持ってくる
  – この行為をスプレーと呼ぶ.splay(𝑥)
  – ただし,回転のさせ方にちょっと工夫


• ならし 𝑂(log 𝑛) 時間で操作 (ポテンシャル解析)
• コンテスト界でそこそこ人気

                                  47
他色々: スプレー木
                 回転ルール: 下図を覚えさえすれば OK

                  z                    x                            x

             y                             y                                  z

        x                                       z                        y




                                                                 普通にやるとこっち
•   x が上に行くようにどんどん回転!                                              になっちゃう

•   ただし,2 つ親まで見る
     – そこまで直線になってたら直線のままになるように y, x の順で回転 (上図)
     – そうなってなかったら普通に x を上に行かせる回転 2 連発
•   詳しくは http://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%97%E3%83%AC%E3%83%BC%E6%9C%A8
                                                                                  48
他色々: Block Linked List
• 平方分割をリストでやろう的な物
• サイズ   𝑛 程度のブロックに分けて,スキップできるように


• ブロックのサイズが変化してきたら調整
  – 2 𝑛 を超えたら 2 つに分割
  – 連続する 2 ブロックのサイズの和が    𝑛/2 未満になったら併合
  – こうしとけば常にどこでも 𝑂( 𝑛) で辿れる!


• Wikipedia の中国語にだけ載ってる
(木じゃないですが似たような機能ができるので仲間ということで)



                                          49
他色々: Skip List


                [http://en.wikipedia.org/wiki/Skip_list]

• リストの階層
  – 最下層は通常のソートされた連結リスト
  – 層 𝑖 に存在する要素は確率 0.5 で層 𝑖 + 1 に存在

• 高い層をできるだけ使って移動,𝑂 log 𝑛

• Path-copying による永続化ができない

(やっぱり木じゃないですが似たような機能ができるので仲間)


                                                           50
平衡二分探索木まとめ
• まずはもっと容易な道具を検討!
 – 配列, リスト, std::map,BIT,セグメント木,バケット法
 – 必要な場所だけ作るセグメント木

• 実装が楽な平衡二分探索木を選ぼう
 – 今回: Treap / Randomized Binary Search Tree
 – 他: スプレー木, Scapegoat 木, Block Linked List, Skip List

• 実装しよう
 – insert / erase ベース     vs.   merge / split ベース
 – 更新遅延


                                                         51

Mais conteúdo relacionado

Mais procurados

様々な全域木問題
様々な全域木問題様々な全域木問題
様々な全域木問題tmaehara
 
プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法Takuya Akiba
 
競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性Hibiki Yamashiro
 
勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとはTakuya Akiba
 
最小カットを使って「燃やす埋める問題」を解く
最小カットを使って「燃やす埋める問題」を解く最小カットを使って「燃やす埋める問題」を解く
最小カットを使って「燃やす埋める問題」を解くshindannin
 
指数時間アルゴリズム入門
指数時間アルゴリズム入門指数時間アルゴリズム入門
指数時間アルゴリズム入門Yoichi Iwata
 
AtCoder Beginner Contest 021 解説
AtCoder Beginner Contest 021 解説AtCoder Beginner Contest 021 解説
AtCoder Beginner Contest 021 解説AtCoder Inc.
 
ウェーブレット木の世界
ウェーブレット木の世界ウェーブレット木の世界
ウェーブレット木の世界Preferred Networks
 
Rolling Hashを殺す話
Rolling Hashを殺す話Rolling Hashを殺す話
Rolling Hashを殺す話Nagisa Eto
 
AtCoder Beginner Contest 023 解説
AtCoder Beginner Contest 023 解説AtCoder Beginner Contest 023 解説
AtCoder Beginner Contest 023 解説AtCoder Inc.
 
図と実装で理解する『木構造入門』
図と実装で理解する『木構造入門』図と実装で理解する『木構造入門』
図と実装で理解する『木構造入門』Proktmr
 
AtCoder Beginner Contest 015 解説
AtCoder Beginner Contest 015 解説AtCoder Beginner Contest 015 解説
AtCoder Beginner Contest 015 解説AtCoder Inc.
 
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方Kensuke Otsuki
 
競プロは社会の役に立たない+ベンチャー企業の話 (NPCA夏合宿OB講演).pdf
競プロは社会の役に立たない+ベンチャー企業の話 (NPCA夏合宿OB講演).pdf競プロは社会の役に立たない+ベンチャー企業の話 (NPCA夏合宿OB講演).pdf
競プロは社会の役に立たない+ベンチャー企業の話 (NPCA夏合宿OB講演).pdfcatupper
 
AtCoder Beginner Contest 011 解説
AtCoder Beginner Contest 011 解説AtCoder Beginner Contest 011 解説
AtCoder Beginner Contest 011 解説AtCoder Inc.
 

Mais procurados (20)

様々な全域木問題
様々な全域木問題様々な全域木問題
様々な全域木問題
 
プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法
 
Convex Hull Trick
Convex Hull TrickConvex Hull Trick
Convex Hull Trick
 
競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性
 
勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは
 
最小カットを使って「燃やす埋める問題」を解く
最小カットを使って「燃やす埋める問題」を解く最小カットを使って「燃やす埋める問題」を解く
最小カットを使って「燃やす埋める問題」を解く
 
指数時間アルゴリズム入門
指数時間アルゴリズム入門指数時間アルゴリズム入門
指数時間アルゴリズム入門
 
AtCoder Beginner Contest 021 解説
AtCoder Beginner Contest 021 解説AtCoder Beginner Contest 021 解説
AtCoder Beginner Contest 021 解説
 
ウェーブレット木の世界
ウェーブレット木の世界ウェーブレット木の世界
ウェーブレット木の世界
 
Rolling Hashを殺す話
Rolling Hashを殺す話Rolling Hashを殺す話
Rolling Hashを殺す話
 
動的計画法を極める!
動的計画法を極める!動的計画法を極める!
動的計画法を極める!
 
Rolling hash
Rolling hashRolling hash
Rolling hash
 
AtCoder Beginner Contest 023 解説
AtCoder Beginner Contest 023 解説AtCoder Beginner Contest 023 解説
AtCoder Beginner Contest 023 解説
 
RMQ クエリ処理
RMQ クエリ処理RMQ クエリ処理
RMQ クエリ処理
 
双対性
双対性双対性
双対性
 
図と実装で理解する『木構造入門』
図と実装で理解する『木構造入門』図と実装で理解する『木構造入門』
図と実装で理解する『木構造入門』
 
AtCoder Beginner Contest 015 解説
AtCoder Beginner Contest 015 解説AtCoder Beginner Contest 015 解説
AtCoder Beginner Contest 015 解説
 
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
 
競プロは社会の役に立たない+ベンチャー企業の話 (NPCA夏合宿OB講演).pdf
競プロは社会の役に立たない+ベンチャー企業の話 (NPCA夏合宿OB講演).pdf競プロは社会の役に立たない+ベンチャー企業の話 (NPCA夏合宿OB講演).pdf
競プロは社会の役に立たない+ベンチャー企業の話 (NPCA夏合宿OB講演).pdf
 
AtCoder Beginner Contest 011 解説
AtCoder Beginner Contest 011 解説AtCoder Beginner Contest 011 解説
AtCoder Beginner Contest 011 解説
 

Destaque

Introduction to DHT with Chord
Introduction to DHT with ChordIntroduction to DHT with Chord
Introduction to DHT with ChordHiroya Nagao
 
【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス
【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス
【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティスUnity Technologies Japan K.K.
 
ChordアルゴリズムによるDHT入門
ChordアルゴリズムによるDHT入門ChordアルゴリズムによるDHT入門
ChordアルゴリズムによるDHT入門Hiroya Nagao
 
An Internal of LINQ to Objects
An Internal of LINQ to ObjectsAn Internal of LINQ to Objects
An Internal of LINQ to ObjectsYoshifumi Kawai
 
深さ優先探索による塗りつぶし
深さ優先探索による塗りつぶし深さ優先探索による塗りつぶし
深さ優先探索による塗りつぶしAtCoder Inc.
 
【Unity道場スペシャル 2017札幌】最適化をする前に覚えておきたい技術 -札幌編-
【Unity道場スペシャル 2017札幌】最適化をする前に覚えておきたい技術 -札幌編-【Unity道場スペシャル 2017札幌】最適化をする前に覚えておきたい技術 -札幌編-
【Unity道場スペシャル 2017札幌】最適化をする前に覚えておきたい技術 -札幌編-Unity Technologies Japan K.K.
 

Destaque (6)

Introduction to DHT with Chord
Introduction to DHT with ChordIntroduction to DHT with Chord
Introduction to DHT with Chord
 
【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス
【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス
【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス
 
ChordアルゴリズムによるDHT入門
ChordアルゴリズムによるDHT入門ChordアルゴリズムによるDHT入門
ChordアルゴリズムによるDHT入門
 
An Internal of LINQ to Objects
An Internal of LINQ to ObjectsAn Internal of LINQ to Objects
An Internal of LINQ to Objects
 
深さ優先探索による塗りつぶし
深さ優先探索による塗りつぶし深さ優先探索による塗りつぶし
深さ優先探索による塗りつぶし
 
【Unity道場スペシャル 2017札幌】最適化をする前に覚えておきたい技術 -札幌編-
【Unity道場スペシャル 2017札幌】最適化をする前に覚えておきたい技術 -札幌編-【Unity道場スペシャル 2017札幌】最適化をする前に覚えておきたい技術 -札幌編-
【Unity道場スペシャル 2017札幌】最適化をする前に覚えておきたい技術 -札幌編-
 

Mais de Takuya Akiba

分散深層学習 @ NIPS'17
分散深層学習 @ NIPS'17分散深層学習 @ NIPS'17
分散深層学習 @ NIPS'17Takuya Akiba
 
Learning Convolutional Neural Networks for Graphs
Learning Convolutional Neural Networks for GraphsLearning Convolutional Neural Networks for Graphs
Learning Convolutional Neural Networks for GraphsTakuya Akiba
 
TCO15 Algorithm Round 2C 解説
TCO15 Algorithm Round 2C 解説TCO15 Algorithm Round 2C 解説
TCO15 Algorithm Round 2C 解説Takuya Akiba
 
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説ACM-ICPC 世界大会 2015 問題 K "Tours" 解説
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説Takuya Akiba
 
大規模グラフ解析のための乱択スケッチ技法
大規模グラフ解析のための乱択スケッチ技法大規模グラフ解析のための乱択スケッチ技法
大規模グラフ解析のための乱択スケッチ技法Takuya Akiba
 
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-Takuya Akiba
 
Cache-Oblivious データ構造入門 @DSIRNLP#5
Cache-Oblivious データ構造入門 @DSIRNLP#5Cache-Oblivious データ構造入門 @DSIRNLP#5
Cache-Oblivious データ構造入門 @DSIRNLP#5Takuya Akiba
 
平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズム平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズムTakuya Akiba
 
大規模ネットワークの性質と先端グラフアルゴリズム
大規模ネットワークの性質と先端グラフアルゴリズム大規模ネットワークの性質と先端グラフアルゴリズム
大規模ネットワークの性質と先端グラフアルゴリズムTakuya Akiba
 
大規模グラフアルゴリズムの最先端
大規模グラフアルゴリズムの最先端大規模グラフアルゴリズムの最先端
大規模グラフアルゴリズムの最先端Takuya Akiba
 

Mais de Takuya Akiba (10)

分散深層学習 @ NIPS'17
分散深層学習 @ NIPS'17分散深層学習 @ NIPS'17
分散深層学習 @ NIPS'17
 
Learning Convolutional Neural Networks for Graphs
Learning Convolutional Neural Networks for GraphsLearning Convolutional Neural Networks for Graphs
Learning Convolutional Neural Networks for Graphs
 
TCO15 Algorithm Round 2C 解説
TCO15 Algorithm Round 2C 解説TCO15 Algorithm Round 2C 解説
TCO15 Algorithm Round 2C 解説
 
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説ACM-ICPC 世界大会 2015 問題 K "Tours" 解説
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説
 
大規模グラフ解析のための乱択スケッチ技法
大規模グラフ解析のための乱択スケッチ技法大規模グラフ解析のための乱択スケッチ技法
大規模グラフ解析のための乱択スケッチ技法
 
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-
 
Cache-Oblivious データ構造入門 @DSIRNLP#5
Cache-Oblivious データ構造入門 @DSIRNLP#5Cache-Oblivious データ構造入門 @DSIRNLP#5
Cache-Oblivious データ構造入門 @DSIRNLP#5
 
平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズム平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズム
 
大規模ネットワークの性質と先端グラフアルゴリズム
大規模ネットワークの性質と先端グラフアルゴリズム大規模ネットワークの性質と先端グラフアルゴリズム
大規模ネットワークの性質と先端グラフアルゴリズム
 
大規模グラフアルゴリズムの最先端
大規模グラフアルゴリズムの最先端大規模グラフアルゴリズムの最先端
大規模グラフアルゴリズムの最先端
 

Último

論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNetToru Tamaki
 
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Yuma Ohgami
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムsugiuralab
 
論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A surveyToru Tamaki
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Danieldanielhu54
 
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...Toru Tamaki
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略Ryo Sasaki
 
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)Hiroki Ichikura
 
SOPを理解する 2024/04/19 の勉強会で発表されたものです
SOPを理解する       2024/04/19 の勉強会で発表されたものですSOPを理解する       2024/04/19 の勉強会で発表されたものです
SOPを理解する 2024/04/19 の勉強会で発表されたものですiPride Co., Ltd.
 
TSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdfTSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdftaisei2219
 

Último (10)

論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet
 
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システム
 
論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Daniel
 
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
 
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
 
SOPを理解する 2024/04/19 の勉強会で発表されたものです
SOPを理解する       2024/04/19 の勉強会で発表されたものですSOPを理解する       2024/04/19 の勉強会で発表されたものです
SOPを理解する 2024/04/19 の勉強会で発表されたものです
 
TSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdfTSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdf
 

プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~

  • 1. 2012/3/20 NTTデータ駒場研修所 (情報オリンピック春合宿) プログラミングコンテストでの データ構造2 ~平衡二分探索木編~ 東京大学情報理工学系研究科 秋葉 拓哉 1
  • 2. 自己紹介 • 秋葉 拓哉 / @iwiwi • 東京大学 情報理工学系研究科 コンピュータ科学専攻 • プログラミングコンテスト好き • プログラミングコンテストチャレンジブック 2
  • 3. データ構造たち (もちろん他にもありますが) • 二分ヒープ • 組み込み辞書 (std::map) • Union-Find 木 初級編 • Binary Indexed Tree コンテストでの データ構造1 • セグメント木 (2010 年) • バケット法 中級編 • 平衡二分探索木 • 動的木 本講義 • (永続データ構造) 3
  • 4. 話すこと 1. 平衡二分探索木 2. 動的木 3. 永続データ構造 (時間あれば) • ちょっとむずい! 「へ~」ぐらいの気持ちでも OK! • アイディアだけじゃなくて,実装にまで立ち入り,簡潔 に実装するテクを伝授したい 4
  • 6. 普通の二分探索木 7 2 15 1 5 10 17 4 8 11 16 19 6
  • 7. 普通の二分探索木: 検索 (find) 10 を検索 7 7と比較 15と比較 2 15 発見 1 5 10 17 4 8 11 16 19 7
  • 8. 普通の二分探索木: 挿入 (insert) 7と比較 7 6 を挿入 2と比較 2 15 5と比較 1 5 10 17 4 6 8 11 16 19 追加 8
  • 9. 普通の二分探索木: 削除 (erase) 15 を削除 7 15 削除 2 11 1 5 10 17 4 8 11 16 19 9
  • 10. 普通の二分探索木の偏り 1, 2, 3, 4, … の順に挿入すると…? 1 2 高さが 𝑂 𝑛 ! 3 処理に 𝑂 𝑛 時間! 4 5 やばすぎ! こういった意地悪に耐える工夫をする二分探索木 = 平衡二分探索木が必要! 10
  • 11. …その前に! 平衡二分探索木は本当に必要? • いつものじゃダメ? – 配列,線形リスト – std::set, std::map – Binary Indexed Tree,バケット法,セグメント木 • 実装が面倒なので楽に避けられたら避けたい • 実際のとこ,本当に必要になる問題はレア 11
  • 12. 範囲の大きな Range Minimum Query • ある区間の最小値を答えてください • ある場所に数値を書いてください ただし場所は 1~109.デフォルト値は ∞ . そんな大きな配列作れない… セグメント木じゃできない… (´・_・`) セグメント木でもできるよ ( ・`д・´) クエリ先読みして座標圧縮しとけばいいよ 12
  • 13. 範囲の大きな Range Minimum Query • ある区間の最小値を答えてください • ある場所に数値を書いてください ただし場所は 1~109.デフォルト値は ∞ . でもクエリ先読みできないかも… (情オリの interactive とか,計算したら出てくるとか) (´・_・`) 必要な場所だけ作るセグメント木でいいよ ( ・`д・´) 13
  • 14. 必要な場所だけ作るセグメント木 2 3 2 3 ∞ 8 2 5 3 ∞ 8 2 ∞ 5 3 ∞ ∞ ∞ 8 2 ∞ • 以下が全てデフォルト値になってるノードは要らない • 𝑂(クエリ数 𝐥𝐨𝐠(場所の範囲)) のノードしかできない • 𝑂(𝐥𝐨𝐠 場所の範囲 ) でクエリを処理できる 春季選考合宿 2011 Day4 Apple 参照 14
  • 15. 例2 反転のある Range Minimum Query • ある区間の最小値を答えてください • ある区間を左右反転してください 反転なんてできない… (´・_・`) ( ・`д・´) ・・・ おとなしく平衡二分探索木を書こう! 15
  • 16. 平衡二分探索木 (&仲間) 超いっぱいあります AVL 木,赤黒木,AA 木,2-3 木,2-3-4 木,スプレー木, Scapegoat 木,Treap,Randomized Binary Search Tree,Tango 木,Block Linked List,Skip List,… • ガチ勢: 赤黒木 (std::map とか) – (定数倍的な意味で) かなり高速 – でも実装が少し面倒 • コンテスト勢: 実装が楽なのを組もう! 16
  • 17. コンテストでの平衡二分探索木 • 大抵,列を管理するために使われる (探索木という感じより,ただの順序を持った列を高機能に扱う感じが多い) • よく必要になるもの – 𝑘 番目に挿入 (insert),𝑘 番目を削除 (erase) – 0, 𝑘 と 𝑘, 𝑛 の 2 つの列に分割 (split) – 2 つの列を連結 (merge) – 値に関する質問・更新 (sum, add 等) これをサポートする木を作ろう! 17
  • 18. Treap の思想 • 根がランダムに選ばれるようにする! – 全ノードが等確率で根になるようにする – 部分木に関しても再帰的に,根をランダムに選ぶこ とを繰り返す • これだけで高さが 𝑂 log 𝑛 • なぜ? 乱択クイックソートと同じ. 18
  • 19. Treap の思想 Treap / RBST 乱択クイックソート ランダムに選ばれた ランダムに選ばれた 根 ピボット 根より ↓ 根より ↓ 小さい値 大きい値 ↑ ↑ ピボットより ピボットより 小さい値 大きい値 19
  • 20. Treap の思想 (別解釈) 普通だと,挿入順は木にどう影響する? c c c c b b d b d a ナイーブな二分探索木に c → b → d → a と挿入 先に挿入したものが上,後に挿入したものが下 20
  • 21. Treap の思想 (別解釈) • 普通の二分探索木でも,もしランダム順に挿入 されてたら必ず高さ 𝑂 log 𝑛 • 実際の挿入順に構わず,ランダム順に挿入され たかのように扱おう! – 常に std::random_shuffle した後で挿入されたかのう 21
  • 22. Treap • 乱数を用いる平衡二分探索木 • 各ノードは,キーの他,優先度を持つ – 優先度は挿入時にランダムで決める キー 優先度 22
  • 23. Treap 以下の 2 つの条件を常に保つ 1. キーを見ると二分探索木 2. 優先度を見ると二分ヒープ (Tree + Heap なので Treap という名前らしい…ワロスwww) 大 優 先 度 小 小 キー 大 23
  • 24. Treap • 優先度が最高のやつが根になる – これは,全ノード等確率! → 平衡! – 部分木に関しても再帰的に同様 大 優 先 度 小 小 キー 大 24
  • 25. Treap の実装法 大まかに 2 つの方法があります insert-erase ベース (insert, erase を実装し,それらの組み合わせで merge, split) merge-split ベース (merge, split を実装し,それらの組み合わせで insert, erase) 25
  • 26. Treap 実装: ノード構造体 struct node_t { int val; // 値 node_t *ch[2]; // = {左, 右}; int pri; // 優先度 int cnt; // 部分木のサイズ int sum; // 部分木の値の和 node_t(int v, double p) : val(v), pri(p), cnt(1), sum(v) { ch[0] = ch[1] = NULL; } }; 26
  • 27. Treap 実装: update int count(node_t *t) { return !t ? 0 : t->cnt; } int sum(node_t *t) { return !t ? 0 : t->sum; } node_t *update(node_t *t) { t->cnt = count(t->ch[0]) + count(t->ch[1]) + 1; t->sum = sum(t->ch[0]) + sum(t->ch[1]) + t->val; return t; // 便利なので t 返しとく } 部分木に関する情報を計算しなおす 子が変わった時などに必ず呼ぶようにする 27
  • 28. Treap 実装: insert (insert-erase ベース) まず優先度を無視して普通に挿入 28
  • 29. Treap 実装: insert (insert-erase ベース) 優先度の条件が満たされるまで回転して上へ 29
  • 31. Treap 実装: 回転 (insert-erase ベース) node_t *rotate(node_t *t, int b) { node_t *s = t->ch[1 - b]; t->ch[1 - b] = s->ch[b]; s->ch[b] = t; update(t); update(s); return s; } 子を,別の変数でなく,配列にすると, 左右の回転が 1 つの関数でできる 親の親のポインタを張り替えなくて良いのは, 各操作が常に部分木の根を返すように実装してるから (次) 31
  • 32. Treap 実装: insert (insert-erase ベース) // t が根となっている木の k 番目に 値 val,優先度 pri のノード挿入 // 根のノードを返す node_t *insert(node_t *t, int k, int val, double pri) { if (!t) return new node_t(val, pri); int c = count(t->ch[0]), b = (k > c); t->ch[b] = insert(t->ch[b], k - (b ? (c + 1) : 0), val, pri); update(t); if (t->pri > t->ch[b]->pri) t = rotate(t, 1 - b); } このように,新しい親のポインタを返す実装にすると楽 (親はたまに変わるので.) 32
  • 33. Treap 実装: erase (insert-erase ベース) 1. 削除したいノードを葉まで持っていく – 削除したいノードの優先度を最低にする感じ – 回転を繰り返す 2. そしたら消すだけ 33
  • 34. Treap 実装: merge / split (insert-erase ベース) • insert, erase が出来たら merge, split は超簡単 • merge(𝑙, 𝑟) – 優先度最強のノード 𝑝 を作る – 𝑝 の左の子を 𝑙,右の子を 𝑟 にする – 𝑝 を erase • split(𝑡, 𝑘) – 優先度最強のノード 𝑝 を木 𝑡 の 𝑘 番目に挿入 – 𝑝 の左の子と右の子をそっと取り出す 34
  • 35. Treap 実装: insert / erase (merge-split ベース) • 逆に,merge, split が出来たら insert, erase は超簡単 • insert(木 t, 場所 k, 値 v) – 木 t を場所 k で split – 左の部分木,値 v のノードだけの木,右の部分木を merge • erase(木 t, 場所 k) – 木 t を場所 k - 1 と場所 k で 3 つに split (split 2 回やればいい) – 一番左と一番右の部分木を merge 今度は, merge, split を直接実装してみよう 35
  • 36. Treap 実装: merge (merge-split ベース) a a b + = A b A B C D B + C D • 優先度の高い方の根を新しい根にする • 再帰的に merge 36
  • 37. Treap 実装: merge (merge-split ベース) node_t *merge(node_t *l, node_t *r) { if (!l || !r) return !l ? r : l; if (l->pri > r->pri) { // 左の部分木の根のほうが優先度が高い場合 l->rch = merge(l->rch, r); return update(l); } else { // 右の部分木の根のほうが優先度が高い場合 r->lch = merge(l, r->lch); return update(r); } } ※ merge-split ベースだと,子を ch[2] みたいに配列で管理するメリットは薄い 上では代わりに lch, rch としてしまっている 37
  • 38. Treap 実装: split (merge-split ベース) split は優先度の事を何も考えないで再帰的に切るだけ (部分木内の任意のノードは根より優先度小なので大丈夫) pair<node_t*, node_t*> split(node_t *t, int k) { // [0, k), [k, n) if (!t) return make_pair(NULL, NULL); if (k <= count(t->lch)) { pair<node_t*, node_t*> s = split(t->lch, k); t->lch = s.second; return make_pair(s.first, update(t)); } else { pair<node_t*, node_t*> s = split(t->rch, k - count(t->lch) - 1); t->rch = s.first; return make_pair(update(t), s.second); } } 38
  • 39. Treap 実装法の比較 insert-erase ベース (insert, erase を実装し,それらの組み合わせで merge, split) merge-split ベース (merge, split を実装し,それらの組み合わせで insert, erase) • どっちでも良いです,好きな方で • ただ,個人的には,merge-split ベースのほうが遥かに楽!! – 回転が必要ない,を筆頭に,頭を使わなくて済む – コードも少し短い – あと,コンテストでは,insert, erase より merge, split が必要にな ることの方が多い 39
  • 40. その他,実装について • malloc・new – 解放・メモリリークやオーバーヘッドが気になる? – グローバル変数としてノードの配列を 1 つ作っておき,そこか ら 1 つずつ取って使うと良い • merge-split ベースでの真面目な insert 1. 優先度が insert 先の木より低ければ再帰的に insert 2. そうでなければ,insert 先の木を split してそいつらを子に – という真面目な実装をすると,定数倍すこし高速 – erase も同様:再帰していって merge 40
  • 41. 例題 反転のある Range Minimum Query • ある区間の最小値を答えてください • ある区間を左右反転してください …結局これはどうやるの? 反転って? (´・_・`) 2 つの方法があるよ ( ・`д・´) 41
  • 42. 反転: 方法 1 真面目に反転を処理する struct node_t { int val; // 値 node_t *ch[2]; // = {左, 右}; int pri; // 優先度 int cnt; // 部分木のサイズ int min; // 部分木の値の最小 (RMQ のため) bool rev; // 部分木が反転していることを表すフラグ … }; まずは構造体にフィールドを追加 42
  • 43. 反転: 方法 1 区間 [l, r) を反転したいとする 1. 場所 l, r で split → 3 つの木を a, b, c とする 2. b の根ノードの rev フラグをトグル 3. 木 a, b, c を merge rev フラグはどのように扱う? 43
  • 44. 反転: 方法 1 void push(node_t *t) { if (t->rev) { • rev フラグの扱い swap(t->lch, t->rch); if (t->lch) t->lch->rev ^= true; // 子に反転を伝搬 if (t->rch) t->rch->rev ^= true; // 子に反転を伝搬 t->rev = false; } } rev フラグによる反転を実際に反映する関数 push ノードにアクセスするたびに最初にこれを呼ぶようにする セグメント木における更新遅延と同様のテクニック (いっぱい push 書きます,書き忘れに注意!) ※数値の更新なども同様に,フラグ的変数作って push する (区間への一様な加算など) 44
  • 45. 反転: 方法 2 はじめから 2 本の列をツリー t1, t2 で管理 • t1:順向き • t2:逆向き 区間 [l, r) を反転したいとする • t1 の [l, r) と,t2 の [l, r) を split して切り出す • 交換して merge t1 1 2 3 4 5 6 簡単. (ただし,無理なケースも.) t2 6 5 4 3 2 1 45
  • 46. 他色々: RBST (Randomized Binary Search Tree) • Treap と同様に,ランダムなノードを根に来させる • ただし,ノードに優先度など余分な情報が不要! • merge(a, b) – n ノードの木 a と m ノードの木 b マージの場合 – 全体 (n + m) ノードから根が等確率で選ばれていれば良い 𝑛 𝑚 – 確率 で a の根を新しい根,確率 で b の根を新しい根に 𝑛+𝑚 𝑛+𝑚 – これを,必要に応じて乱数を発生して決める Treap よりこっちのほうが構造体がシンプルになってカッコイイかも 46
  • 47. 他色々: スプレー木 • 一見よくわからん回転を繰り返す • でも実はそれで平衡される!という不思議系データ構造 • 基本: ノード 𝑥 にアクセスする際,そのついでに回転を 繰り返して 𝑥 を木の根まで持ってくる – この行為をスプレーと呼ぶ.splay(𝑥) – ただし,回転のさせ方にちょっと工夫 • ならし 𝑂(log 𝑛) 時間で操作 (ポテンシャル解析) • コンテスト界でそこそこ人気 47
  • 48. 他色々: スプレー木 回転ルール: 下図を覚えさえすれば OK z x x y y z x z y 普通にやるとこっち • x が上に行くようにどんどん回転! になっちゃう • ただし,2 つ親まで見る – そこまで直線になってたら直線のままになるように y, x の順で回転 (上図) – そうなってなかったら普通に x を上に行かせる回転 2 連発 • 詳しくは http://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%97%E3%83%AC%E3%83%BC%E6%9C%A8 48
  • 49. 他色々: Block Linked List • 平方分割をリストでやろう的な物 • サイズ 𝑛 程度のブロックに分けて,スキップできるように • ブロックのサイズが変化してきたら調整 – 2 𝑛 を超えたら 2 つに分割 – 連続する 2 ブロックのサイズの和が 𝑛/2 未満になったら併合 – こうしとけば常にどこでも 𝑂( 𝑛) で辿れる! • Wikipedia の中国語にだけ載ってる (木じゃないですが似たような機能ができるので仲間ということで) 49
  • 50. 他色々: Skip List [http://en.wikipedia.org/wiki/Skip_list] • リストの階層 – 最下層は通常のソートされた連結リスト – 層 𝑖 に存在する要素は確率 0.5 で層 𝑖 + 1 に存在 • 高い層をできるだけ使って移動,𝑂 log 𝑛 • Path-copying による永続化ができない (やっぱり木じゃないですが似たような機能ができるので仲間) 50
  • 51. 平衡二分探索木まとめ • まずはもっと容易な道具を検討! – 配列, リスト, std::map,BIT,セグメント木,バケット法 – 必要な場所だけ作るセグメント木 • 実装が楽な平衡二分探索木を選ぼう – 今回: Treap / Randomized Binary Search Tree – 他: スプレー木, Scapegoat 木, Block Linked List, Skip List • 実装しよう – insert / erase ベース vs. merge / split ベース – 更新遅延 51