Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

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

5 433 vues

Publié le

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

Publié dans : Technologie
  • Soyez le premier à commenter

動的計画法入門(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. おわり  ご清聴、ありがとうございました。

×