プログラミングコンテストでの動的計画法

Takuya Akiba
Takuya AkibaStudent at The University of Tokyo
2010/03/19 代々木オリンピックセンター (情報オリンピック春合宿)




  プログラミングコンテストでの
       動的計画法
             秋葉 拓哉 / (iwi)
はじめに
• 「動的計画法」は英語で
     「Dynamic Programming」
  と言います.

• 略して「DP」とよく呼ばれます.

• スライドでも以後使います.
全探索と DP の関係

動的計画法のアイディア
ナップサック問題とは?
• n 個の品物がある
• 品物 i は重さ wi, 価値 vi
• 重さの合計が U を超えないように選ぶ
 – 1 つの品物は 1 つまで
• この時の価値の合計の最大値は?

     品物 1    品物 2          品物 n
     重さ w1   重さ w2   ・・・   重さ wn
     価値 v1   価値 v2         価値 vn
ナップサック問題の例
        品物 1     品物 2     品物 3     品物 4
U=5     w1 = 2   w2 = 1   w3 = 3   w4 = 2
        v1 = 3   v2 = 2   v3 = 4   v4 = 2




        品物 1     品物 2     品物 3     品物 4
答え 7    w1 = 2   w2 = 1   w3 = 3   w4 = 2
        v1 = 3   v2 = 2   v3 = 4   v4 = 2
全探索のアルゴリズム (1/3)
             品物 1      品物 2      品物 3      品物 4
   U = 10    w1 = 2    w2 = 2    w3 = 3    w4 = 2
             v1 = 3    v2 = 2    v3 = 4    v4 = 2



                      品物 1      品物 2      品物 3      品物 4
            U=8       w1 = 2    w2 = 2    w3 = 3    w4 = 2
                      v1 = 3    v2 = 2    v3 = 4    v4 = 2
品物 1 を
 使う


                      品物 1      品物 2      品物 3      品物 4
            U = 10    w1 = 2    w2 = 2    w3 = 3    w4 = 2
品物 1 を                v1 = 3    v2 = 2    v3 = 4    v4 = 2
使わない




         品物 1 から順に,使う場合・使わない場合の両方を試す
全探索のアルゴリズム (2/3)

// i 番目以降の品物で,重さの総和が u 以下となるように選ぶ
int search(int i, int u) {
          if (i == n) {           // もう品物は残っていない
                       return 0;
          } else if (u < w[i]) { // この品物は入らない
                       return search(i + 1, u);
          } else {                // 入れない場合と入れる場合を両方試す
                       int res1 = search(i + 1, u);
                       int res2 = search(i + 1, u - w[i]) + v[i];
                       return max(res1, res2);
          }
}



              外からは search(0, U) を呼ぶ
全探索のアルゴリズム (3/3)
// i 番目以降の品物で,重さの総和が u 以下となるように選ぶ
int search(int i, int u) {
          …
}




    このままでは,指数時間かかる
      (U が大きければ,2n 通り試してしまう)


      少しでも n が大きくなると
       まともに計算できない
改善のアイディア
// i 番目以降の品物で,重さの総和が u 以下となるように選ぶ
int search(int i, int u) {
          …
}




       大事な考察 : search(i, u) は
  i, u が同じなら常に同じ結果
同じ結果になる例

               品物 1            品物 2     品物 3     品物 4
U = 10 - 2     w1 = 2          w2 = 2   w3 = 3   w4 = 2   ・・・
               v1 = 3          v2 = 2   v3 = 4   v4 = 2




               品物 1            品物 2     品物 3     品物 4
U = 10 - 2     w1 = 2          w2 = 2   w3 = 3   w4 = 2   ・・・
               v1 = 3          v2 = 2   v3 = 4   v4 = 2




     品物 3 以降の最適な選び方は同じ
     • 2 + search(3, 10 – 2)
                                2 度全く同じ探索をしてしまう
     • 3 + search(3, 10 – 2)
動的計画法のアイディア
• 同じ探索を 2 度しないようにする

• search(i, u) を
  – 各 (i, u) について一度だけ計算
  – その値を何度も使いまわす


• 名前はいかついが,非常に簡単な話
  – 簡単な話だが,効果は高い(次)
動的計画法の計算量
• 全探索では指数時間だった
 – O(2n),N が少し大きくなるだけで計算できな
   い

• DP では,各 (i, u) について 1 度計算する
 – なので,約 N × U 回の計算で終了
 – O(NU) (擬多項式時間,厳密にはこれでも指数時間)

• N が大きくても大丈夫になった
メモ探索,漸化式

動的計画法の実装
2 つの方法
• ナップサック問題の続き
 – DP のプログラムを完成させましょう


• 以下の 2 つの方法について述べます
 1. 再帰関数のメモ化
 2. 漸化式+ループ
方法 1 : メモ化 (1/2)
// i 番目以降の品物で,重さの総和が u 以下となるように選ぶ
int search(int i, int u) {
          …
}



• 全探索の関数 search を改造する
 – はじめての (i, u) なら
   • 探索をして,その値を配列に記録

 – はじめてでない (i, u) なら
   • 記録しておいた値を返す
方法 1 : メモ化 (2/2)
bool done[MAX_N][MAX_U + 1];   // すでに計算したかどうかの記録
int memo[MAX_N][MAX_U + 1];    // 計算した値の記録

// i 番目以降の品物で,重さの総和が u 以下となるように選ぶ
int search(int i, int u) {
          if (done[i][u]) return memo[i][u]; // もう計算してた?
          int res;

        ...                    // 値を res に計算

        done[i][u] = true;     // 計算したことを記憶
        memo[i][u] = res;      // 値を記憶
        return res;
}


done を全て false に初期化し,search(0, U) を呼ぶ
方法 2 : 漸化式 (1/3)
• 全探索のプログラムを式に

                     search(i + 1, u)
search(i, u) = max
                     search(i + 1, u – wi) + vi
  (場合分けは省略)




• このような式を,漸化式と呼ぶ
 – パラメータが異なる自分自身との間の式
方法 2 : 漸化式 (2/3)
                      search(i + 1, u)
search(i, u) = max
                      search(i + 1, u – wi) + vi

• i が大きい方から計算してゆけばよい
 – 下のような表を埋めてゆくイメージ

 i \u   0     1   2   3    …          i の大きい方から
  1                                   埋めてゆく
  2
                                      埋める際に,既
  3                                   に埋めた部分の
  …                                   値を利用する
方法 2 : 漸化式 (3/3)
int dp[MAX_N + 1][MAX_U + 1];              // 埋めてゆく「表」

int do_dp() {
     for (int u = 0; u <= U; u++) dp[N][u] = 0;       // 境界条件

     for (int i = N – 1; i >= 0; i--) {
           for (int u = 0; u <= U; u++) {
                 if (u < w[i])               // u が小さく,商品 i が使えない
                       dp[i][u] = dp[i + 1][u];
                 else                        // 商品 i が使える
                       dp[i][u] = max( dp[i + 1][u] , dp[i + 1][u – w[i]] + v[i] );
           }
     }
     return dp[0][U];
}
どちらの方法を使うべきか
• 好きな方を使いましょう
 – どちらでも,多くの場合大丈夫です


• 後で,比較をします
 – どちらも使いこなせるようになるのがベスト
呼び方
• 方法 1 (再帰関数のメモ化)をメモ探索
• 方法 2 (漸化式+ループ)を動的計画法 (DP)
と呼ぶことがあります.

– 「動的計画法」と言ったとき
 • メモ探索を含む場合と含まない場合がある
 • 2 つにあまり差がない状況では区別されない
   – アルゴリズムの話としては(ほぼ)差がない
   – 書き方としての違い
Tips
• 大きすぎる配列をローカル変数として確
  保しないこと
 – 大きな配列はグローバルに取る
  • スタック領域とヒープ領域
  • スタック領域の制限
 – 大丈夫な場合もあるが,多くはない(IOI 等)
知らない問題を動的計画法で解く

動的計画法の作り方
動的計画法の問題を解く
• おすすめの流れ (初心者)
    1. 全探索によるアルゴリズムを考える
    2. 動的計画法のアルゴリズムにする

• ここまでのナップサック問題の説明と同
  様の流れで解けばよい
    – パターンにしてしまおう

•   実際には,全探索を考えるのと,漸化式を考えるのは,ほぼ同じ行為
簡単な例で復習
• フィボナッチ数列
int fib(int n) {
      if (n == 0) return 0;
      if (n == 1) return 1;
      return fib(n – 1) + fib(n – 2);
}


• これを,先ほどと同様の流れで
   – メモ探索
   – 動的計画法(ループ)
に書き直してみてください.
非常に簡単ですが,やり方の確認なので,形式的に行うようにしましょう
メモ探索
bool done[MAX_N + 1];
int memo[MAX_N + 1];

int fib(int n) {
      if (n == 0) return 0;
      if (n == 1) return 1;

     if (done[n]) return memo[n];

     done[n] = true;
     return memo[n] = fib(n – 1) + fib(n- 2);
}
ループ
int dp[MAX_N + 1];

int fib(int n) {
      dp[0] = 0;
      dp[1] = 1;

     for (int i = 2; i <= n; i++) dp[i] = dp[i – 1] + dp[i – 2];

     return dp[n];
}
どんな全探索を考えれば良いのか

動的計画法にできる全探索
ナップサック問題
• ナップサック問題の際に使った全探索

// i 番目以降の品物で,重さの総和が u 以下となるように選ぶ
int search(int i, int u) {
          …
}




• これ以外にも全探索は考えられる
良くない全探索 (1/3)
// i 番目以降の品物で,重さの総和が u 以下となるように選ぶ
// そこまでに選んだ品物の価値の和が vsum
int search(int i, int u, int vsum) {
          if (i == n) {           // もう品物は残っていない
                       return vsum;
          } else if (u < w[i]) { // この品物は入らない
                       return search(i + 1, u, vsum);
          } else {                // 入れない場合と入れる場合を両方試す
                       int res1 = search(i + 1, u, vsum);
                       int res2 = search(i + 1, u - w[i], vsum + v[i]);
                       return max(res1, res2);
          }
}



            外からは search(0, U, 0) を呼ぶ
良くない全探索 (2/3)

    // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ
    // そこまでに選んだ品物の価値の和が vsum
    int search(int i, int u, int vsum) {
          ...
    }



• 引数が増え,動的計画法に出来ない
     – 各 (i, u, vsum) で一度ずつ計算すれば出来るが,効率が悪い

•   このような方針で書くと枝刈りができたりするので,不自然な書き方ではないが…
同じ結果になる例 (再)

               品物 1            品物 2     品物 3     品物 4
U = 10 - 2     w1 = 2          w2 = 2   w3 = 3   w4 = 2   ・・・
               v1 = 3          v2 = 2   v3 = 4   v4 = 2




               品物 1            品物 2     品物 3     品物 4
U = 10 - 2     w1 = 2          w2 = 2   w3 = 3   w4 = 2   ・・・
               v1 = 3          v2 = 2   v3 = 4   v4 = 2




     品物 3 以降の最適な選び方は同じ
     • 2 + search(3, 10 – 2)
                                2 度全く同じ探索をしてしまう
     • 3 + search(3, 10 – 2)
良くない全探索 (3/3)
• 引数が増え,動的計画法に出来ない
 – 各 (i, u, vsum) で一度ずつ計算すれば出来るが,効率が悪い


• このようなことを防ぐため,

         全探索の関数の引数は,
         その後の探索に影響のある
          最低限のものにする

• 選び方は,残る容量にはよるが,そこまでの価値によら
  ない.なので vsum は引数にしない
その他の注意
• グローバル変数を変化させるようなこと
  はしてはいけない

• 状態をできるだけシンプルにする
 – × 「どの商品を使ったか」・・・ 2n
 – ○「今までに使った商品の重さの和」 ・・・ U

• ここで扱った事項は,アルゴリズムを考える際だけでなく,
  動的計画法のアルゴリズムをより効率的にしようとする際に
  も重要です.
DP の問題をいくつか簡単に

典型的問題
経路の数 (1/2)
• ●から ● へ移動する経路の数
 – 右か上に進める




• ある地点からの経路の数は,そこまでの経路によらない
経路の数 (2/2)
• 通れるなら
 – route(x, y) = route(x-1, y) + route(x, y-1)
• ×なら
 – route(x, y) = 0
最長増加部分列(LIS) (1/2)
• 数字の列が与えられる
• 増加する部分列で,最も長いもの

• 後ろの増加部分列は,
  一番最後に使った数
  のみよる
最長増加部分列(LIS) (2/2)
• lis(i) := i を最後に使った LIS の長さ

• lis(i) = maxj { lis(j) + 1 }
   – ただし,j < i かつ aj < ai




                                 lis(i)   1 2 2 3 3 4 4 5 4
最長共通部分列 (LCS)
• 2 つの文字列が与えられ,その両方に含ま
  れる最も長い文字列
 – X = “ABCBDAB”,Y = “BDCABA”   ⇒   “BCBA”


• (X の i 文字目以降, Y の j 文字目以降)
 で作れる共通部分列は同じ
最後に
DP に強くなるために
• 慣れるために,自分で何回かやってみま
  しょう

• 典型的な問題を知りましょう
 – ナップサック問題
  • 0-1,個数無制限
 – 最長増加部分列 (LIS)
 – 最長共通部分列 (LCS)
 – 連鎖行列積
ナップサック問題=DP? (1/2)
• ナップサック問題,ただし:
 – 商品の個数 n ≦ 20,
 – 価値 vi ≦ 109, 重さ wi ≦ 109
 – 重さの和の上限 U ≦ 109

• ナップサック問題だから DP だ!
      とは言わないように!
ナップサック問題=DP? (2/2)
• DP ・・・ O(nU) ⇒ 20 × 109
• 全探索 ・・・O(2n) ⇒ 220 ≒ 106

• 全探索の方が高速な場合もある

• DP に限らず,アルゴリズムは手段であり
  目的ではありません
  – 無理に使わない
おしまい

お疲れ様でした
補足スライド
集合を状態にする DP

ビット DP
TSP (1/2)
• 巡回セールスマン問題 (TSP)
 – n 個の都市があって,それぞれの間の距離がわかって
   います
 – 都市 1 から全部の都市に訪れ,都市 1 に戻る
 – 最短の移動距離は?

   1        1       2        1       1       2

                3                        3
        5                        5
    4               6        4               6


            2                        2
   4                3    4                   3
TSP (2/2)
• NP 困難問題なので,効率的な解法は期待
  できない

• 全探索は O(n!) 時間
 – n ≦ 10 程度まで


• DP を用いると O(n2 2n) 時間にできる
 – n ≦ 16 程度まで
TSP の DP の状態 (1/2)
• 状態は,下の 2 つの組
 – 既に訪れている街はどれか ・・・ 2n 通り
 – 今いる街はどれか ・・・ n 通り


• そこまでの順番に関わらず,その後の最適な道
  のりは等しい




    赤色:そこまでの道のり,緑色:そこからの最適な道のり
TSP の DP の状態 (2/2)
• 「既に訪れている街はどれか」(2n 通り)

• これを,ビットマスクで表現
 – i ビット目が 1 なら訪れている
 – i ビット目が 0 なら訪れていない
ビットマスクの利点
• 状態が 0 から 2n-1 にエンコードされ,そ
  のまま配列を利用できる

• 集合の包含関係が数値の大小関係
 – 集合 A, B について A ⊂ B ならば
 – ビット表現で a ≦ b
ビットマスクを用いた全探索
int N, D[30][30];   // 頂点数,距離行列

// 今頂点 v に居て,既に訪れた頂点のビットマスクが b
int tsp(int v, int b) {
      if (b == (1 << N) - 1) return D[v][0]; // 全部訪れた

     int res = INT_MAX;
     for (int w = 0; w < N; w++) {
           if (b & (1 << w)) continue;               // もう訪れた
           res = min(res, D[v][w] + tsp(w, b | (1 << w)));
     }
     return res;
}



                    外からは tsp(0, 1) を呼ぶ
動的計画法にする
int tsp(int v, int b) {
      ...
}



• 配列 memo[MAX_N][1 << MAX_N] 等を作っ
  て,tsp(v, b) の結果を記録すれば良い

• ループで記述するのも簡単
   – b は降順に回せばよい
2 つの実装方法の違い

メモ探索 VS 動的計画法
メモ探索の利点
• ループの順番を考えなくて良い
 – ツリー,DAG の問題


• 状態生成が楽になる場合がある
 – 状態が複雑な問題


• 起こり得ない状態を計算しなくてよい
動的計画法 (ループ) の利点
• メモリを節約できる場合がある
 – 今後の計算で必要になる部分のみ覚えていれ
   ば良い


• 実装が短い

• 再帰呼び出しのオーバーヘッドがない
配る DP と貰う DP
• 配る DP
 – 各場所の値を計算してゆくのではなく,各場
   所の値を他のやつに加えてゆくような DP
 – メモ探索ではできない


• 確率,期待値の問題で有用なことがある
おしまい
1 of 59

Recommended

Union find(素集合データ構造) by
Union find(素集合データ構造)Union find(素集合データ構造)
Union find(素集合データ構造)AtCoder Inc.
168.9K views18 slides
プログラミングコンテストでのデータ構造 by
プログラミングコンテストでのデータ構造プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造Takuya Akiba
104.8K views73 slides
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~ by
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~Takuya Akiba
57.7K views51 slides
明日使えないすごいビット演算 by
明日使えないすごいビット演算明日使えないすごいビット演算
明日使えないすごいビット演算京大 マイコンクラブ
63.4K views60 slides
勉強か?趣味か?人生か?―プログラミングコンテストとは by
勉強か?趣味か?人生か?―プログラミングコンテストとは勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとはTakuya Akiba
71.8K views69 slides
指数時間アルゴリズム入門 by
指数時間アルゴリズム入門指数時間アルゴリズム入門
指数時間アルゴリズム入門Yoichi Iwata
44.7K views98 slides

More Related Content

What's hot

プログラミングコンテストでの乱択アルゴリズム by
プログラミングコンテストでの乱択アルゴリズムプログラミングコンテストでの乱択アルゴリズム
プログラミングコンテストでの乱択アルゴリズムTakuya Akiba
26.8K views37 slides
双対性 by
双対性双対性
双対性Yoichi Iwata
25.8K views89 slides
プログラミングコンテストでのデータ構造 2 ~動的木編~ by
プログラミングコンテストでのデータ構造 2 ~動的木編~プログラミングコンテストでのデータ構造 2 ~動的木編~
プログラミングコンテストでのデータ構造 2 ~動的木編~Takuya Akiba
46K views30 slides
プログラムを高速化する話 by
プログラムを高速化する話プログラムを高速化する話
プログラムを高速化する話京大 マイコンクラブ
242.3K views120 slides
ウェーブレット木の世界 by
ウェーブレット木の世界ウェーブレット木の世界
ウェーブレット木の世界Preferred Networks
55.4K views67 slides

What's hot(20)

プログラミングコンテストでの乱択アルゴリズム by Takuya Akiba
プログラミングコンテストでの乱択アルゴリズムプログラミングコンテストでの乱択アルゴリズム
プログラミングコンテストでの乱択アルゴリズム
Takuya Akiba26.8K views
プログラミングコンテストでのデータ構造 2 ~動的木編~ by Takuya Akiba
プログラミングコンテストでのデータ構造 2 ~動的木編~プログラミングコンテストでのデータ構造 2 ~動的木編~
プログラミングコンテストでのデータ構造 2 ~動的木編~
Takuya Akiba46K views
色々なダイクストラ高速化 by yosupo
色々なダイクストラ高速化色々なダイクストラ高速化
色々なダイクストラ高速化
yosupo25K views
ラムダ計算入門 by Eita Sugimoto
ラムダ計算入門ラムダ計算入門
ラムダ計算入門
Eita Sugimoto33.7K views
最適化超入門 by Takami Sato
最適化超入門最適化超入門
最適化超入門
Takami Sato174.7K views
様々な全域木問題 by tmaehara
様々な全域木問題様々な全域木問題
様々な全域木問題
tmaehara37.7K views
Rolling Hashを殺す話 by Nagisa Eto
Rolling Hashを殺す話Rolling Hashを殺す話
Rolling Hashを殺す話
Nagisa Eto4.1K views
平面グラフと交通ネットワークのアルゴリズム by Takuya Akiba
平面グラフと交通ネットワークのアルゴリズム平面グラフと交通ネットワークのアルゴリズム
平面グラフと交通ネットワークのアルゴリズム
Takuya Akiba26.2K views
組合せ最適化入門:線形計画から整数計画まで by Shunji Umetani
組合せ最適化入門:線形計画から整数計画まで組合せ最適化入門:線形計画から整数計画まで
組合せ最適化入門:線形計画から整数計画まで
Shunji Umetani77.1K views
Word Tour: One-dimensional Word Embeddings via the Traveling Salesman Problem... by joisino
Word Tour: One-dimensional Word Embeddings via the Traveling Salesman Problem...Word Tour: One-dimensional Word Embeddings via the Traveling Salesman Problem...
Word Tour: One-dimensional Word Embeddings via the Traveling Salesman Problem...
joisino3.4K views

Similar to プログラミングコンテストでの動的計画法

WUPC2012 by
WUPC2012WUPC2012
WUPC2012Dai Hamada
1.6K views87 slides
Algorithm 速いアルゴリズムを書くための基礎 by
Algorithm 速いアルゴリズムを書くための基礎Algorithm 速いアルゴリズムを書くための基礎
Algorithm 速いアルゴリズムを書くための基礎Kenji Otsuka
5.2K views64 slides
20120127 nhn by
20120127 nhn20120127 nhn
20120127 nhnYuki Manno
713 views40 slides
ナンプレ解析ツール by
ナンプレ解析ツールナンプレ解析ツール
ナンプレ解析ツールkstmshinshu
2K views28 slides
純粋関数型アルゴリズム入門 by
純粋関数型アルゴリズム入門純粋関数型アルゴリズム入門
純粋関数型アルゴリズム入門Kimikazu Kato
6.6K views33 slides
実践・最強最速のアルゴリズム勉強会 第二回講義資料(ワークスアプリケーションズ & AtCoder) by
実践・最強最速のアルゴリズム勉強会 第二回講義資料(ワークスアプリケーションズ & AtCoder)実践・最強最速のアルゴリズム勉強会 第二回講義資料(ワークスアプリケーションズ & AtCoder)
実践・最強最速のアルゴリズム勉強会 第二回講義資料(ワークスアプリケーションズ & AtCoder)AtCoder Inc.
31.2K views107 slides

Similar to プログラミングコンテストでの動的計画法(10)

Algorithm 速いアルゴリズムを書くための基礎 by Kenji Otsuka
Algorithm 速いアルゴリズムを書くための基礎Algorithm 速いアルゴリズムを書くための基礎
Algorithm 速いアルゴリズムを書くための基礎
Kenji Otsuka5.2K views
20120127 nhn by Yuki Manno
20120127 nhn20120127 nhn
20120127 nhn
Yuki Manno713 views
ナンプレ解析ツール by kstmshinshu
ナンプレ解析ツールナンプレ解析ツール
ナンプレ解析ツール
kstmshinshu2K views
純粋関数型アルゴリズム入門 by Kimikazu Kato
純粋関数型アルゴリズム入門純粋関数型アルゴリズム入門
純粋関数型アルゴリズム入門
Kimikazu Kato6.6K views
実践・最強最速のアルゴリズム勉強会 第二回講義資料(ワークスアプリケーションズ & AtCoder) by AtCoder Inc.
実践・最強最速のアルゴリズム勉強会 第二回講義資料(ワークスアプリケーションズ & AtCoder)実践・最強最速のアルゴリズム勉強会 第二回講義資料(ワークスアプリケーションズ & AtCoder)
実践・最強最速のアルゴリズム勉強会 第二回講義資料(ワークスアプリケーションズ & AtCoder)
AtCoder Inc.31.2K views
Sanpo by oupc
SanpoSanpo
Sanpo
oupc908 views
何もないところから数を作る by Taketo Sano
何もないところから数を作る何もないところから数を作る
何もないところから数を作る
Taketo Sano4.8K views
自然言語処理はじめました - Ngramを数え上げまくる by phyllo
自然言語処理はじめました - Ngramを数え上げまくる自然言語処理はじめました - Ngramを数え上げまくる
自然言語処理はじめました - Ngramを数え上げまくる
phyllo12.4K views
130323 slide all by ikea0064
130323 slide all130323 slide all
130323 slide all
ikea0064291 views

More from Takuya Akiba

分散深層学習 @ NIPS'17 by
分散深層学習 @ NIPS'17分散深層学習 @ NIPS'17
分散深層学習 @ NIPS'17Takuya Akiba
18.2K views40 slides
Learning Convolutional Neural Networks for Graphs by
Learning Convolutional Neural Networks for GraphsLearning Convolutional Neural Networks for Graphs
Learning Convolutional Neural Networks for GraphsTakuya Akiba
16K views36 slides
TCO15 Algorithm Round 2C 解説 by
TCO15 Algorithm Round 2C 解説TCO15 Algorithm Round 2C 解説
TCO15 Algorithm Round 2C 解説Takuya Akiba
5.3K views56 slides
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説 by
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説ACM-ICPC 世界大会 2015 問題 K "Tours" 解説
ACM-ICPC 世界大会 2015 問題 K "Tours" 解説Takuya Akiba
6K views84 slides
大規模グラフ解析のための乱択スケッチ技法 by
大規模グラフ解析のための乱択スケッチ技法大規模グラフ解析のための乱択スケッチ技法
大規模グラフ解析のための乱択スケッチ技法Takuya Akiba
38.2K views43 slides
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩- by
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-
乱択データ構造の最新事情 -MinHash と HyperLogLog の最近の進歩-Takuya Akiba
32.4K views37 slides

More from Takuya Akiba(9)

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

Recently uploaded

救急医学会2023(発表).pdf by
救急医学会2023(発表).pdf救急医学会2023(発表).pdf
救急医学会2023(発表).pdfHirohisa Shimizu
62 views39 slides
GP10.pdf by
GP10.pdfGP10.pdf
GP10.pdfMasato FUKUHARA
153 views20 slides
GL10.pdf by
GL10.pdfGL10.pdf
GL10.pdfMasato FUKUHARA
18 views20 slides
松本良多 インタビュー | ArtUp Mi 2023.pdf by
松本良多 インタビュー | ArtUp Mi 2023.pdf松本良多 インタビュー | ArtUp Mi 2023.pdf
松本良多 インタビュー | ArtUp Mi 2023.pdfkrimsadatv
6 views12 slides
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『理学院の変更点、出題のねらいと出題例』 by
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『理学院の変更点、出題のねらいと出題例』東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『理学院の変更点、出題のねらいと出題例』
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『理学院の変更点、出題のねらいと出題例』Tokyo Institute of Technology
641 views4 slides
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『物質理工学院の変更点、出題のねらいと出題例』 by
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『物質理工学院の変更点、出題のねらいと出題例』東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『物質理工学院の変更点、出題のねらいと出題例』
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『物質理工学院の変更点、出題のねらいと出題例』Tokyo Institute of Technology
461 views4 slides

Recently uploaded(6)

松本良多 インタビュー | ArtUp Mi 2023.pdf by krimsadatv
松本良多 インタビュー | ArtUp Mi 2023.pdf松本良多 インタビュー | ArtUp Mi 2023.pdf
松本良多 インタビュー | ArtUp Mi 2023.pdf
krimsadatv6 views
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『理学院の変更点、出題のねらいと出題例』 by Tokyo Institute of Technology
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『理学院の変更点、出題のねらいと出題例』東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『理学院の変更点、出題のねらいと出題例』
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『理学院の変更点、出題のねらいと出題例』
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『物質理工学院の変更点、出題のねらいと出題例』 by Tokyo Institute of Technology
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『物質理工学院の変更点、出題のねらいと出題例』東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『物質理工学院の変更点、出題のねらいと出題例』
東京工業大学の新しい総合型・学校推薦型選抜(一般枠・女子枠)『物質理工学院の変更点、出題のねらいと出題例』

プログラミングコンテストでの動的計画法

  • 1. 2010/03/19 代々木オリンピックセンター (情報オリンピック春合宿) プログラミングコンテストでの 動的計画法 秋葉 拓哉 / (iwi)
  • 2. はじめに • 「動的計画法」は英語で 「Dynamic Programming」 と言います. • 略して「DP」とよく呼ばれます. • スライドでも以後使います.
  • 4. ナップサック問題とは? • n 個の品物がある • 品物 i は重さ wi, 価値 vi • 重さの合計が U を超えないように選ぶ – 1 つの品物は 1 つまで • この時の価値の合計の最大値は? 品物 1 品物 2 品物 n 重さ w1 重さ w2 ・・・ 重さ wn 価値 v1 価値 v2 価値 vn
  • 5. ナップサック問題の例 品物 1 品物 2 品物 3 品物 4 U=5 w1 = 2 w2 = 1 w3 = 3 w4 = 2 v1 = 3 v2 = 2 v3 = 4 v4 = 2 品物 1 品物 2 品物 3 品物 4 答え 7 w1 = 2 w2 = 1 w3 = 3 w4 = 2 v1 = 3 v2 = 2 v3 = 4 v4 = 2
  • 6. 全探索のアルゴリズム (1/3) 品物 1 品物 2 品物 3 品物 4 U = 10 w1 = 2 w2 = 2 w3 = 3 w4 = 2 v1 = 3 v2 = 2 v3 = 4 v4 = 2 品物 1 品物 2 品物 3 品物 4 U=8 w1 = 2 w2 = 2 w3 = 3 w4 = 2 v1 = 3 v2 = 2 v3 = 4 v4 = 2 品物 1 を 使う 品物 1 品物 2 品物 3 品物 4 U = 10 w1 = 2 w2 = 2 w3 = 3 w4 = 2 品物 1 を v1 = 3 v2 = 2 v3 = 4 v4 = 2 使わない 品物 1 から順に,使う場合・使わない場合の両方を試す
  • 7. 全探索のアルゴリズム (2/3) // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ int search(int i, int u) { if (i == n) { // もう品物は残っていない return 0; } else if (u < w[i]) { // この品物は入らない return search(i + 1, u); } else { // 入れない場合と入れる場合を両方試す int res1 = search(i + 1, u); int res2 = search(i + 1, u - w[i]) + v[i]; return max(res1, res2); } } 外からは search(0, U) を呼ぶ
  • 8. 全探索のアルゴリズム (3/3) // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ int search(int i, int u) { … } このままでは,指数時間かかる (U が大きければ,2n 通り試してしまう) 少しでも n が大きくなると まともに計算できない
  • 9. 改善のアイディア // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ int search(int i, int u) { … } 大事な考察 : search(i, u) は i, u が同じなら常に同じ結果
  • 10. 同じ結果になる例 品物 1 品物 2 品物 3 品物 4 U = 10 - 2 w1 = 2 w2 = 2 w3 = 3 w4 = 2 ・・・ v1 = 3 v2 = 2 v3 = 4 v4 = 2 品物 1 品物 2 品物 3 品物 4 U = 10 - 2 w1 = 2 w2 = 2 w3 = 3 w4 = 2 ・・・ v1 = 3 v2 = 2 v3 = 4 v4 = 2 品物 3 以降の最適な選び方は同じ • 2 + search(3, 10 – 2) 2 度全く同じ探索をしてしまう • 3 + search(3, 10 – 2)
  • 11. 動的計画法のアイディア • 同じ探索を 2 度しないようにする • search(i, u) を – 各 (i, u) について一度だけ計算 – その値を何度も使いまわす • 名前はいかついが,非常に簡単な話 – 簡単な話だが,効果は高い(次)
  • 12. 動的計画法の計算量 • 全探索では指数時間だった – O(2n),N が少し大きくなるだけで計算できな い • DP では,各 (i, u) について 1 度計算する – なので,約 N × U 回の計算で終了 – O(NU) (擬多項式時間,厳密にはこれでも指数時間) • N が大きくても大丈夫になった
  • 14. 2 つの方法 • ナップサック問題の続き – DP のプログラムを完成させましょう • 以下の 2 つの方法について述べます 1. 再帰関数のメモ化 2. 漸化式+ループ
  • 15. 方法 1 : メモ化 (1/2) // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ int search(int i, int u) { … } • 全探索の関数 search を改造する – はじめての (i, u) なら • 探索をして,その値を配列に記録 – はじめてでない (i, u) なら • 記録しておいた値を返す
  • 16. 方法 1 : メモ化 (2/2) bool done[MAX_N][MAX_U + 1]; // すでに計算したかどうかの記録 int memo[MAX_N][MAX_U + 1]; // 計算した値の記録 // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ int search(int i, int u) { if (done[i][u]) return memo[i][u]; // もう計算してた? int res; ... // 値を res に計算 done[i][u] = true; // 計算したことを記憶 memo[i][u] = res; // 値を記憶 return res; } done を全て false に初期化し,search(0, U) を呼ぶ
  • 17. 方法 2 : 漸化式 (1/3) • 全探索のプログラムを式に search(i + 1, u) search(i, u) = max search(i + 1, u – wi) + vi (場合分けは省略) • このような式を,漸化式と呼ぶ – パラメータが異なる自分自身との間の式
  • 18. 方法 2 : 漸化式 (2/3) search(i + 1, u) search(i, u) = max search(i + 1, u – wi) + vi • i が大きい方から計算してゆけばよい – 下のような表を埋めてゆくイメージ i \u 0 1 2 3 … i の大きい方から 1 埋めてゆく 2 埋める際に,既 3 に埋めた部分の … 値を利用する
  • 19. 方法 2 : 漸化式 (3/3) int dp[MAX_N + 1][MAX_U + 1]; // 埋めてゆく「表」 int do_dp() { for (int u = 0; u <= U; u++) dp[N][u] = 0; // 境界条件 for (int i = N – 1; i >= 0; i--) { for (int u = 0; u <= U; u++) { if (u < w[i]) // u が小さく,商品 i が使えない dp[i][u] = dp[i + 1][u]; else // 商品 i が使える dp[i][u] = max( dp[i + 1][u] , dp[i + 1][u – w[i]] + v[i] ); } } return dp[0][U]; }
  • 20. どちらの方法を使うべきか • 好きな方を使いましょう – どちらでも,多くの場合大丈夫です • 後で,比較をします – どちらも使いこなせるようになるのがベスト
  • 21. 呼び方 • 方法 1 (再帰関数のメモ化)をメモ探索 • 方法 2 (漸化式+ループ)を動的計画法 (DP) と呼ぶことがあります. – 「動的計画法」と言ったとき • メモ探索を含む場合と含まない場合がある • 2 つにあまり差がない状況では区別されない – アルゴリズムの話としては(ほぼ)差がない – 書き方としての違い
  • 22. Tips • 大きすぎる配列をローカル変数として確 保しないこと – 大きな配列はグローバルに取る • スタック領域とヒープ領域 • スタック領域の制限 – 大丈夫な場合もあるが,多くはない(IOI 等)
  • 24. 動的計画法の問題を解く • おすすめの流れ (初心者) 1. 全探索によるアルゴリズムを考える 2. 動的計画法のアルゴリズムにする • ここまでのナップサック問題の説明と同 様の流れで解けばよい – パターンにしてしまおう • 実際には,全探索を考えるのと,漸化式を考えるのは,ほぼ同じ行為
  • 25. 簡単な例で復習 • フィボナッチ数列 int fib(int n) { if (n == 0) return 0; if (n == 1) return 1; return fib(n – 1) + fib(n – 2); } • これを,先ほどと同様の流れで – メモ探索 – 動的計画法(ループ) に書き直してみてください. 非常に簡単ですが,やり方の確認なので,形式的に行うようにしましょう
  • 26. メモ探索 bool done[MAX_N + 1]; int memo[MAX_N + 1]; int fib(int n) { if (n == 0) return 0; if (n == 1) return 1; if (done[n]) return memo[n]; done[n] = true; return memo[n] = fib(n – 1) + fib(n- 2); }
  • 27. ループ int dp[MAX_N + 1]; int fib(int n) { dp[0] = 0; dp[1] = 1; for (int i = 2; i <= n; i++) dp[i] = dp[i – 1] + dp[i – 2]; return dp[n]; }
  • 29. ナップサック問題 • ナップサック問題の際に使った全探索 // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ int search(int i, int u) { … } • これ以外にも全探索は考えられる
  • 30. 良くない全探索 (1/3) // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ // そこまでに選んだ品物の価値の和が vsum int search(int i, int u, int vsum) { if (i == n) { // もう品物は残っていない return vsum; } else if (u < w[i]) { // この品物は入らない return search(i + 1, u, vsum); } else { // 入れない場合と入れる場合を両方試す int res1 = search(i + 1, u, vsum); int res2 = search(i + 1, u - w[i], vsum + v[i]); return max(res1, res2); } } 外からは search(0, U, 0) を呼ぶ
  • 31. 良くない全探索 (2/3) // i 番目以降の品物で,重さの総和が u 以下となるように選ぶ // そこまでに選んだ品物の価値の和が vsum int search(int i, int u, int vsum) { ... } • 引数が増え,動的計画法に出来ない – 各 (i, u, vsum) で一度ずつ計算すれば出来るが,効率が悪い • このような方針で書くと枝刈りができたりするので,不自然な書き方ではないが…
  • 32. 同じ結果になる例 (再) 品物 1 品物 2 品物 3 品物 4 U = 10 - 2 w1 = 2 w2 = 2 w3 = 3 w4 = 2 ・・・ v1 = 3 v2 = 2 v3 = 4 v4 = 2 品物 1 品物 2 品物 3 品物 4 U = 10 - 2 w1 = 2 w2 = 2 w3 = 3 w4 = 2 ・・・ v1 = 3 v2 = 2 v3 = 4 v4 = 2 品物 3 以降の最適な選び方は同じ • 2 + search(3, 10 – 2) 2 度全く同じ探索をしてしまう • 3 + search(3, 10 – 2)
  • 33. 良くない全探索 (3/3) • 引数が増え,動的計画法に出来ない – 各 (i, u, vsum) で一度ずつ計算すれば出来るが,効率が悪い • このようなことを防ぐため, 全探索の関数の引数は, その後の探索に影響のある 最低限のものにする • 選び方は,残る容量にはよるが,そこまでの価値によら ない.なので vsum は引数にしない
  • 34. その他の注意 • グローバル変数を変化させるようなこと はしてはいけない • 状態をできるだけシンプルにする – × 「どの商品を使ったか」・・・ 2n – ○「今までに使った商品の重さの和」 ・・・ U • ここで扱った事項は,アルゴリズムを考える際だけでなく, 動的計画法のアルゴリズムをより効率的にしようとする際に も重要です.
  • 36. 経路の数 (1/2) • ●から ● へ移動する経路の数 – 右か上に進める • ある地点からの経路の数は,そこまでの経路によらない
  • 37. 経路の数 (2/2) • 通れるなら – route(x, y) = route(x-1, y) + route(x, y-1) • ×なら – route(x, y) = 0
  • 38. 最長増加部分列(LIS) (1/2) • 数字の列が与えられる • 増加する部分列で,最も長いもの • 後ろの増加部分列は, 一番最後に使った数 のみよる
  • 39. 最長増加部分列(LIS) (2/2) • lis(i) := i を最後に使った LIS の長さ • lis(i) = maxj { lis(j) + 1 } – ただし,j < i かつ aj < ai lis(i) 1 2 2 3 3 4 4 5 4
  • 40. 最長共通部分列 (LCS) • 2 つの文字列が与えられ,その両方に含ま れる最も長い文字列 – X = “ABCBDAB”,Y = “BDCABA” ⇒ “BCBA” • (X の i 文字目以降, Y の j 文字目以降) で作れる共通部分列は同じ
  • 42. DP に強くなるために • 慣れるために,自分で何回かやってみま しょう • 典型的な問題を知りましょう – ナップサック問題 • 0-1,個数無制限 – 最長増加部分列 (LIS) – 最長共通部分列 (LCS) – 連鎖行列積
  • 43. ナップサック問題=DP? (1/2) • ナップサック問題,ただし: – 商品の個数 n ≦ 20, – 価値 vi ≦ 109, 重さ wi ≦ 109 – 重さの和の上限 U ≦ 109 • ナップサック問題だから DP だ! とは言わないように!
  • 44. ナップサック問題=DP? (2/2) • DP ・・・ O(nU) ⇒ 20 × 109 • 全探索 ・・・O(2n) ⇒ 220 ≒ 106 • 全探索の方が高速な場合もある • DP に限らず,アルゴリズムは手段であり 目的ではありません – 無理に使わない
  • 48. TSP (1/2) • 巡回セールスマン問題 (TSP) – n 個の都市があって,それぞれの間の距離がわかって います – 都市 1 から全部の都市に訪れ,都市 1 に戻る – 最短の移動距離は? 1 1 2 1 1 2 3 3 5 5 4 6 4 6 2 2 4 3 4 3
  • 49. TSP (2/2) • NP 困難問題なので,効率的な解法は期待 できない • 全探索は O(n!) 時間 – n ≦ 10 程度まで • DP を用いると O(n2 2n) 時間にできる – n ≦ 16 程度まで
  • 50. TSP の DP の状態 (1/2) • 状態は,下の 2 つの組 – 既に訪れている街はどれか ・・・ 2n 通り – 今いる街はどれか ・・・ n 通り • そこまでの順番に関わらず,その後の最適な道 のりは等しい 赤色:そこまでの道のり,緑色:そこからの最適な道のり
  • 51. TSP の DP の状態 (2/2) • 「既に訪れている街はどれか」(2n 通り) • これを,ビットマスクで表現 – i ビット目が 1 なら訪れている – i ビット目が 0 なら訪れていない
  • 52. ビットマスクの利点 • 状態が 0 から 2n-1 にエンコードされ,そ のまま配列を利用できる • 集合の包含関係が数値の大小関係 – 集合 A, B について A ⊂ B ならば – ビット表現で a ≦ b
  • 53. ビットマスクを用いた全探索 int N, D[30][30]; // 頂点数,距離行列 // 今頂点 v に居て,既に訪れた頂点のビットマスクが b int tsp(int v, int b) { if (b == (1 << N) - 1) return D[v][0]; // 全部訪れた int res = INT_MAX; for (int w = 0; w < N; w++) { if (b & (1 << w)) continue; // もう訪れた res = min(res, D[v][w] + tsp(w, b | (1 << w))); } return res; } 外からは tsp(0, 1) を呼ぶ
  • 54. 動的計画法にする int tsp(int v, int b) { ... } • 配列 memo[MAX_N][1 << MAX_N] 等を作っ て,tsp(v, b) の結果を記録すれば良い • ループで記述するのも簡単 – b は降順に回せばよい
  • 56. メモ探索の利点 • ループの順番を考えなくて良い – ツリー,DAG の問題 • 状態生成が楽になる場合がある – 状態が複雑な問題 • 起こり得ない状態を計算しなくてよい
  • 57. 動的計画法 (ループ) の利点 • メモリを節約できる場合がある – 今後の計算で必要になる部分のみ覚えていれ ば良い • 実装が短い • 再帰呼び出しのオーバーヘッドがない
  • 58. 配る DP と貰う DP • 配る DP – 各場所の値を計算してゆくのではなく,各場 所の値を他のやつに加えてゆくような DP – メモ探索ではできない • 確率,期待値の問題で有用なことがある