Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

動的計画法入門(An introduction to Dynamic Programming)

1/8(水)千葉大学CCS勉強会 『動的計画法入門』の資料です。

  • Login to see the comments

動的計画法入門(An introduction to Dynamic Programming)

  1. 1. 的計画法入門
  2. 2. 目次  グラフの基礎知識  無向グラフ、有向グラフ  閉路、DAG、トポロジカル順序  グラフの表現方法  DAGの問題の解き方  最短経路、最長経路  最大/最小の重みの合計  連結しているか  動的計画法とDAG  DAGを使った動的計画法  問題解説  DAGへの落とし方
  3. 3. グラフの基礎知識
  4. 4. グラフとは  丸が線で繋がれた図  丸に情報をいれ、線で情報のつながりを表す  丸に「駅名」、線で「電車でつながっている駅」を表す、 など
  5. 5. グラフとは エッジ ノード エッジ ノード エッジ ノード エッジ ノード エッジ ノード エッジ  丸を「ノード」や「頂点」  線を「エッジ」や「辺」という  あるノードから別のノードに行く行き方を「パス」や 「経路」という  エッジにも情報を持たせることがある。(重み)
  6. 6. 有向グラフ  グラフの中で、一方通行なエッジがあるもの  一方通行のエッジがないものは「無向グラフ」  ノードから出て行くエッジを「出力辺」  ノードに入っていくエッジを「入力辺」  仕事順などを表せる 両方向通行可 の場合
  7. 7. 閉路  違うノードを通り、最初のノードに戻ることができる とき、その経路を「閉路」という。  つまり、一周回って戻ってこれる、ということ。 閉路あり 閉路あり 閉路なし!
  8. 8. DAG  閉路を持たない有向グラフのこと  動的計画法のキモです これもDAG 閉路はない! つまりDAG これは違う
  9. 9. トポロジカル順序  DAGにおいて、「ノードAからノードBにエッジがあ るとき、必ずAがBより前にあるように並び替えた順 序」のこと A B C  トポロジカル順序は、  「A → B → C → D → E」  「A → B → D → C → E」 D E
  10. 10. トポロジカル順序  トポロジカル順序は複数存在するときもある  DAGを仕事の順序を表したグラフと考えると、 「仕事が成立する順序」とも考えられる。  トポロジカル順序にノードを並び替えることを 「トポロジカルソート」という。 A B C A → B → C → D → E または 仕事が成立する。 D E A → B → D → C → E なら
  11. 11. グラフの表現(構造体)  構造体の配列での表現 typedef struct _node { int data; //ノードの情報 } Node; typedef truct _edge { Node *pNode1; Node *pNode2; int weight; //重み } Edge; Node nodes[100]; Edge edges[10000];  色々なグラフを表現可能。しかし若干めんどくさい。
  12. 12. グラフの表現(配列)  配列での表現 int nodes[100]; int nodes[10][20];  ノードが線、長方形、直方体状に並んだグラフを簡単に再 現できる  エッジはプログラミングでどうにか表現する  nodes[i] = nodes[i-3] + 2; // node iとnode i-3を関連付け  動的計画法の問題に適している  ノードの番号(座標)も情報として活用できる  複雑なグラフは再現しにくい
  13. 13. グラフの表現(配列)例  node i と node i-3 を関連付けした場合 i: 0 1 2 3 4 5 6  このグラフはDAGです(トポロジカル順序はiの昇順)  nodes[i] = nodes[i-3] + 2 とすれば、エッジの重み が2のDAGが完成
  14. 14. DAGの問題の 解き方
  15. 15. グラフを使った問題  グラフの問題には、例えば以下があります。  ノードAからノードBに行くとき、  通るエッジ数(ノード数)の最大/最小  通るエッジの重みの合計の最大/最小  総経路数(行き方の場合の数)  ノードAとノードBがつながっているかどうか  実は、グラフが配列を使ったDAGの時は、これらの問 題は簡単に解くことができます
  16. 16. 配列DAGの問題を解く(1)  ノードAからノードBまでのなんちゃら系は、まずト ポロジカル順序で調べていきます。  トポロジカル順序で調べているので、あるノードを調 べている時、そのノードにつながっている入力辺の先の ノード(入力ノード)はすでに調べているはずです。 入力辺 調べる対象 のノード これらは、もう調べられている つまり、値が確定している
  17. 17. 配列DAGの問題を解く(2)  次に、調べ終わっているノードの値から、調べている ノードの値を確定します。  通るエッジの重みの合計の最大/最小なら  あるノードに保存する情報は、「ノードAから、そのノー ドに至るまでの重みの合計の最大/最小」とする  あるノードに至るまでの重みの最大/最小は、その入力 ノードの情報+エッジの重みの中で、最大/最小のもの 4 2 8 3 10 1 ←4+3、2+10、8+1の中で 最大/最小の値を書き込む
  18. 18. 配列DAGの問題を解く(3)  最大/最小を求めるときは、以下の式が便利です。 node[i] = max(node[i],node[j]+edge_val); or node[i] = min(node[i],node[j]+edge_val);  この式をiの全部の入力ノードjに対して実行します  iは固定でjが変わっていき、node[i]を更新するイメージ  例えば、あるノードが、それより6つ前までのノード 全てとつながっている時は(範囲チェックは割愛) for(int k=1;k<=6;k++) { node[i] = min(node[i],node[i-k]+edge_val); }  最大/最小のエッジ数なら、重みを1で固定します
  19. 19. 配列DAGの問題を解く(4)  ノードAからノードBまでの総経路数の問題ならもっ と話は簡単です。  ノードに保存する情報は「ノードAからそのノードま での総経路数」とします  ノードをトポロジカル順序で調べていきます  調べる内容は、ただ入力ノードの値を足すだけです。  「確率」も同様にして求められます。  ノードに確率を保存し、そのp倍を出力先ノードに足す 4 0.4 確率1/4 0.6 確率1/4 2 8 ↑4+2+8通り ↑0.4*1/4 + 0.6*1/4
  20. 20. 配列DAGの問題を解く(5)  「ノードAとノードBが連結しているか」も同じように考えます。  面倒臭かったら、経路数が0かどうかを調べても良いです  ノードに保存する情報は、ノードAとそのノードがつながってい るなら1、そうでないなら0とします  ノードAはノードAと連結していると考えます。  ノードAの値は1、それ以外はとりあえず0  トポロジカル順序で考えた時、全ての入力ノードの内、一つでも 1があれば1、一つもなければ0としていきます  若干効率は悪いですが… 今回はこれで行きましょう 1 0 0 ←1があるので、 1を書き込みます
  21. 21. 配列DAGの問題を解く(6)  初期条件には注意が必要です。  最大を求める問題なら、最初から最大値が決定している ノード以外のノードの値は、-∞で初期化します  実際には-∞は入れられないので、-INTMAXで我慢します  最小ならば、+∞で初期化します  経路数なら、明らかなところを1で、そうでないところ を0で初期化します。  連結しているかどうかは、連結しているかどうかの対象 ノードの値を1に、それ以外を0に初期化します。
  22. 22. 配列DAGの問題を解く(7)  実際に考えてみましょう  線状に並んだ101個のノードが数直線上にある。これ らのノードは「素数個」後のノードと繋がっている  2個後、3個後、5個後、7個後、11個後…と隣接  範囲外のノードは繋がっていないと考える。  この時1個目のノードから101個目のノードに行く  行き方は何通りあるか  最小のエッジ数は何個か
  23. 23. 配列DAGの問題を解く(8)  グラフ i: 0 1 2  トポロジカル順序はiの昇順 3 4 5
  24. 24. 配列DAGの問題を解く(9)  行き方 int nodes[101] = {0}; //行き方を格納 int primes[25] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71 ,73,79,83,89,97}; nodes[0] = 1; //0番に行く行き方だけは1 //トポロジカルソート順に調べる for(int i = 0; i < 101; i++) { for(int j = 0; j < 25; j++) { if (i – primes[j] >= 0) { //範囲チェック nodes[i] += node[i - primes[j]]; } } } std::cout << nodes[100] << std::endl;
  25. 25. 配列DAGの問題を解く(10)  最小のエッジ数 int nodes[101]; //最小のエッジ数を格納 int primes[25] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71 ,73,79,83,89,97}; nodes[0] = 0; //0番は0個のエッジ数で到達できる for(int i = 1; i < 101; i++) nodes[i] = 10000000; //+∞ //トポロジカルソート順に調べる for(int i = 0; i < 101; i++) { for(int j = 0; j < 25; j++) { if (i – primes[j] >= 0) { //範囲チェック nodes[i] = min(nodes[i], nodes[i - primes[j]] + 1); } } } std::cout << nodes[100] << std::endl;
  26. 26. 動的計画法とDAG
  27. 27. 動的計画法とDAG  すでに分かっている答えを組み合わせて、新しい答え を作っていく方法  具体的には、制約が簡単な問題の答えから、より難しい制 約の問題を解いていく。  DP(Dynamic Programming)と言われる  通常の調べ上げより処理が圧倒的に軽い(𝑎 𝑛 → 𝑛 𝑎 )  動的計画法の問題は、基本的にDAGに落とせる  DAGに落とせれば、先程の解法が使える  配列を使ったDAGに落とすことが多いが、その時の添 字も情報として活用する
  28. 28. 例題  100を素数の和で表すとき、その最小項数を求めよ  例:8 = 2+2+2+2 = 2+3+3 = 5+3 数は2 だから、最小項
  29. 29. 例題:ヒント  100を素数の和で表すとき、その最小項数を求めよ  ターゲットの自然数をいきなり分解しようとすると難 しい。  とりあえず、DAGに落とせないか考えよう  dp[i]に「iを素数の和で表すときの、最小項数」を入 れてみる。  なぜこの配列を考えようと思ったのかは、後解説します  dp[i]とdp[i-素数]の関係を考えると…
  30. 30. 例題:解説  dp[i] := iを素数の和で表すときの、最小項数  足した素数分だけ右に行き、使ったエッジ数が1増える  「ノード0からノード100に行くときの、最小エッジ 数を求めよ」と同義  先ほどの問題と全く一緒 i: 0 1 2 3 4 5
  31. 31. 問1  100万円以下の金額が与えられる。10種類の小切手(𝑖 種類目の小切手の金額は𝐴[𝑖](> 0))で支払いをする時、 支払う合計枚数の最小値を出力しなさい。  出典:『最速最強アルゴリズマー養成講座』(編)
  32. 32. 問1:解説  dp[i] := i万円払うときの小切手の最小枚数  1枚使うごとに枚数が増える。iはA[i]だけ増える  「枚数」は「使ったエッジ数」に置き換えられる  例題と同じ!
  33. 33. 問2  将棋の「銀」の動きをするコマが、10*10の盤面の一 番左下にある。35ターンちょうどで、一番右上に行く 進み方は何通りあるか。
  34. 34. 問2:ヒント  地形がある問題は、その地形の形でノードを置くと良 いです。  dp[i][j]など。  その上でノードのつながりを考えます。今回は銀が移 動するので、左上、上、右上、右下、左下のノードと繋 がっていると考えられます  しかしこれだとDAGになりません。こういう時はもう 一次元何かを増やすとうまくいくことが多いです。何か もう一つ制約は無かったでしょうか? 銀の動きをグラフに してみたがDAGにならない→
  35. 35. 問2:解説(1)  今回は、ターン数でもう一次元拡張します。  dp[i][j][k] :=kターンで座標(i,j)に行く行き方  一回移動すると、ターンが1増えるのでノードを一階 層ずらしてつなぎます。 j 全てのノードが一階層下の 左上、上、右上、右下、左下 と繋がっている k i
  36. 36. 問2:解説2  「30ターンちょうどで」→ 「30階層目で」  求めるものは、「ノード(0,0,0) j から(9,9,35)に行く総経路数 を求めよ」と同義 k  これはDAGで、トポロジカル 順序はkの昇順 i
  37. 37. 問2:解説(3) int dp[10][10][36] = {0}; //kターンで(i,j)に行く進み方の数 int dirX[5] = {-1,0,1,-1,1}; //銀の進む方向(x) int dirY[5] = {1,1,1,-1,-1}; //銀の進む方向(y) dp[0][0][0] = 1; for(int k = 1; k <= 35; k++) { //トポロジカル順序はkの昇順 for(int i = 0; i < 10; i++) { for(int j = 0; j < 10; j++) { for(int l=0;l<5;l++) { int px = i - dirX[l]; int py = j - dirY[l]; if(isIN(px,py)) { //範囲チェック dp[i][j][k]+=dp[px][py][k-1]; } } } } } std::cout << dp[9][9][35] << std::endl;
  38. 38. 問3  さいころを振って進むすごろくがある。ただし、ちょ うど6𝑛 マス(𝑛 > 0)進むと、スタートに戻される。20 回さいころを振ったとき、スタート地点から30マス以 上進んでいる確率を求めよ。  出典:『最速最強アルゴリズマー養成講座』(編)
  39. 39. 問3:ヒント  一応、すごろくなので、地形があります。  地形と言っても「何マス進んだか」の一直線ですが…  サイコロを振って進むので、各ノードは6つ先までの ノード全てとつながっていると言えます  ただし、 6𝑛個目のノードにはつなぐときは、スタートに つなぎ変えます  しかし、これではDAGになりません。何か制約を加え てDAGにしましょう。
  40. 40. 問3:解説(1)  「サイコロを振った回数」を加えて次元を拡張します  dp[i][j] := i回サイコロを振った時jマス進んでいる確率  i階層目の各ノードは、i+1階層目の6つ先までのノー ドと繋がっている  6𝑛個目の場合は、i+1階層目のスタートにつなぎ替える。  ある一つのエッジを進む確率は、エッジの出処のノー ドの値の1/6  この値をエッジの先のノードに足していく。 ←1/3*0.4 + 1/3*0.6 0.4 ←1/3*0.4 + 1/3*0.6 0.6 ←1/3*0.4 + 1/3*0.6 皆確率1/3の時
  41. 41. j 問3:解説(2) 0 1 i j: 2 3 4 5 6 7
  42. 42. 問3:解説(3) double dp[21][121]={0}; //i回サイコロを振ってjマス進む確率 dp[0][0] = 1.0; //スタートマスの確率は1 for(int i = 1; i <= 20; i++) { //トポロジカル順序はiの昇順 dp[i][0] = 1.0 / 6.0; for(int j = 1; j < 121; j++) { if (j % 6 != 0) { for(int k = 1; k <= 6; k++) { if (j – k >= 0) { //範囲チェック dp[i][j] += dp[i-1][j-k]/6.0; } } } } } double ans=0.0; for(int j = 30; j < 121; j++) ans += dp[20][j]; std::cout << ans << std::endl;
  43. 43. 問4  ある大小バラバラの1000個以下のビー玉が与えられ る。ビー玉の重さが1~10の整数だとして、これを2人 で分けるとき、合計の重さを均等にすることが可能か求 めなさい  出典:『最速最強アルゴリズマー養成講座』
  44. 44. 問4:ヒント  ビー玉の合計の重さをSとすると、Sが偶数でない場合 不可能。偶数の時、「何個かのビー玉を選んで、合計が S/2となる選び方が存在するか」と言い換えられる。  「~を選んで」の問題の場合、「~をi番目まで考慮し た時の」と機械的に1次元増やすとうまくいく。  これと、合計を配列の添字にすると…
  45. 45. 問4:解説(1)  dp[i][j] := i個目のビー玉まで考慮した時、選んだビー 玉の重さの合計がjとなるか(1:可 0:不可)  「選択」を「 i個目の~まで考慮した時」とした時は、 選択肢の数だけ、エッジが出ます  今回は、選ぶか選ばないかの2つのエッジが出ます  選んだ場合は、i番目のビー玉の重さだけ右にズレて1階層下 のノードとつながります。選ばない場合は直下のノードとつ ながります。  後は、(0,0)のノードと(1000,S/2)のノードが連結し ているかという問題を解くだけです。  もう少し効率のよい方法もあるらしい
  46. 46. 問4:解説(2) j i ビー玉の 重さ2 ビー玉の 重さ1
  47. 47. 問4:解説(3) int dp[1001][5001] = {0}; // i個目のビー玉まで考慮した時、選んだ ビー玉の重さの合計がjとなるか dp[0][0] = 1; int weight[1000]; //ビー玉の重さ int sum = 0; //ビー玉の重さの合計 for(int i = 0; i < 1000; i++) sum += weight[i]; if (sum % 2 == 0) { for(int i = 1; i <= 1000; i++) { //トポロジカル順序 for(int j = 0; j < 5001; j++) { if(j - A[i - 1] >= 0) { //範囲チェック dp[i][j] |= dp[i-1][j-A[i-1]]; } dp[i][j] |= dp[i-1][j]; } } } if (dp[1000][sum/2]) std::cout << “possible” << std::endl; else std::cout << “impossible” << std::endl;
  48. 48. DAGへの落とし方(1)  まず、問題に関わる制約を全て書き出します。  その制約を「dp配列の添字または値にするもの」と 「グラフのつながりを考えるために使用するもの」にグ ループ分けします  大まかには  範囲指定されている制約は配列の添字になりやすい  求めるものは、配列の値になりやすい  その他、問題を解いていく途中に値がコロコロ変わるもの は配列の添字/値になりやすい  選択肢、地形も配列の添字になる  それ以外はグラフのつながりを表すものになる
  49. 49. DAGへの落とし方(2)  特に配列の添字になりやすいもの  ターン数、日数、手数、(試行)回数  合計  地形  選択(「i番目まで考慮した時の~」に変更して添字に)  特にグラフのつながり方を表すものになりやすいもの  移動規則(広い意味での「座標」を移動させるルール)  恒等的に同じ中身の配列
  50. 50. 問1  100万円以下の金額が与えられる。10種類の小切手(𝑖種類 目の小切手の金額は𝐴[𝑖](> 0))で支払いをする時、支払う合 計枚数の最小値を出力しなさい。  dpの添字または値になるもの  金額…問題を解いていく途中にコロコロ変わる。また、範囲指 定がされていて、金額の合計と言い換えられる。  枚数…求めるものだから、dp配列の値になりやすい。コロコロ 変わる。  グラフのつながりを表すもの  小切手の金額の配列…問題文中は常に一定。金額の合計を移動 させるものと考えることができる  結果:dp[i] := i円払うのにかかる合計枚数の最小値
  51. 51. 問2  将棋の「銀」の動きをするコマが、10*10の盤面の一 番左下にある。30ターンちょうどで、一番右上に行く 進み方は何通りあるか。  dpの添字または値になるもの  盤面…立派な地形。添字になりやすい。  ターン…特に添字になりやすいものの代表。解いている途 中にコロコロかわり、30ターン以内という制限もある  通り…求めるものなので値になりやすい。  グラフのつながりを表すもの  コマの動き…移動手段。地形とセットでよく出てくる。  結果:dp[i][j][k] := kターンで(i,j)に行く進み方の数
  52. 52. 問3  さいころを振って進むすごろくがある。ただし、ちょうど6𝑛 マ ス(𝑛 > 0)進むと、スタートに戻される。20回さいころを振ったと き、スタート地点から30マス以上進んでいる確率を求めよ。  dpの添字または値になるもの  回数…添字になりやすい典型です  マス…基本的に、出た目の合計と考えられます。問題を解いている 途中にコロコロ変わり、範囲指定もあります。  確率…求めるものです。値になりやすいです。  グラフのつながりを表すもの  さいころ…移動規則です。進んだマスを増やすものと考えられます。 問題文中は一定です。  戻る…さいころと同じで、移動規則です。  結果:dp[i][j] := i回さいころを振った時jマス進んでいる確率
  53. 53. 問4  ある大小バラバラの1000個以下のビー玉が与えられる。 ビー玉の重さが1~10の整数だとして、これを2人で分ける とき、合計の重さを均等にすることが可能か求めなさい  dpの添字または値になるもの  ビー玉の個数…二人の内どちらかに振り分けると考えれば、選 択と考えられます。範囲指定もあります。添字です。  合計の重さ…「合計」です。添字になります。  グラフのつながりを表すもの  ビー玉の重さ…問題を解く間一定です。合計の重さを移動させ るものと考えることができます  結果:dp[i][j] := i個目のビー玉まで考慮した時、合計の重 さがjとなるか
  54. 54. 参考文献等  「DPの話」  http://d.hatena.ne.jp/Tayama/20111210/132350209 2  DAGと動的計画法の関連についてわかりやすく、詳しく書いて あります。今回扱っていない高度な話題もたくさんあります。  「最速最強アルゴリズマー養成講座」  http://www.itmedia.co.jp/enterprise/articles/1005/15/n ews002_2.html  今回の問題の出典元です。  「プログラミングコンテスト チャレンジブック」  通称蟻本です。動的計画法含め、様々なアルゴリズムについて 幅広く解説されています。
  55. 55. おわり  ご清聴、ありがとうございました。

×