npca summer 2014

180
NPCA夏合宿講義

Upload: okuraofvegetable

Post on 08-Jul-2015

53 views

Category:

Food


1 download

DESCRIPTION

NPCA summer 2014

TRANSCRIPT

Page 1: NPCA summer 2014

NPCA夏合宿講義

Page 2: NPCA summer 2014

● 合宿初参加の身なのに講義を頼まれました

● なにが講義だえらそうに

● 許して

Page 3: NPCA summer 2014

今日するお話

● 全探索

– DFS(深さ優先探索)

– BFS(幅優先探索)

● 動的計画法(DP)

– メモ化再帰

– DP

Page 4: NPCA summer 2014

   全探索

Page 5: NPCA summer 2014

全探索

● 物事には状態がある

– 年齢、身長、etc...

● 状態がどう変化していくか調べたい

Page 6: NPCA summer 2014

全探索

● (例)

1,2,3から一つずつ数字をとっていく時とる順番

は何通りありますか?

– 3! = 6通り

Page 7: NPCA summer 2014

状態の変化

1

2

3

2

3

1

3

1

2

はじめ

3

2

3

1

2

1

Page 8: NPCA summer 2014

状態の変化

1

2

3

2

3

1

3

1

2

はじめ

3

2

3

1

2

1

それぞれが1通りのとり方に対応

Page 9: NPCA summer 2014

状態の変化

1

2

3

2

3

1

3

1

2

はじめ

3

2

3

1

2

1

それぞれが1通りのとり方に対応

Page 10: NPCA summer 2014

状態の変化

1

2

3

2

3

1

3

1

2

はじめ

3

2

3

1

2

1

それぞれが1通りのとり方に対応

Page 11: NPCA summer 2014

状態の変化

1

2

3

2

3

1

3

1

2

はじめ

3

2

3

1

2

1

それぞれが1通りのとり方に対応

Page 12: NPCA summer 2014

状態の変化

1

2

3

2

3

1

3

1

2

はじめ

3

2

3

1

2

1

それぞれが1通りのとり方に対応

Page 13: NPCA summer 2014

状態の変化

1

2

3

2

3

1

3

1

2

はじめ

3

2

3

1

2

1

それぞれが1通りのとり方に対応

Page 14: NPCA summer 2014

全探索

● こうした状態の変化をすべて調べつくしたい● 2つの方法があります

Page 15: NPCA summer 2014

全探索

● こうした状態の変化をすべて調べつくしたい● 2つの方法があります

– DFS(深さ優先探索)

– BFS(幅優先探索)

Page 16: NPCA summer 2014

全探索

● こうした状態の変化をすべて調べつくしたい● 2つの方法があります

– DFS(深さ優先探索)

– BFS(幅優先探索)

● 2つの違い

– 探索する順序

Page 17: NPCA summer 2014

DFS

● こういう状態の遷移があるとする

Page 18: NPCA summer 2014

DFS

1

Page 19: NPCA summer 2014

DFS

1

2

Page 20: NPCA summer 2014

DFS

1

3

2

Page 21: NPCA summer 2014

DFS

1

3

4

2

Page 22: NPCA summer 2014

DFS

1

3

4

2

Page 23: NPCA summer 2014

DFS

1

3

4 5

2

Page 24: NPCA summer 2014

DFS

1

3

4 5

2

Page 25: NPCA summer 2014

DFS

1

3

4 65

2

Page 26: NPCA summer 2014

DFS

1

3

4 65

2

Page 27: NPCA summer 2014

DFS

1

3

4 65

2

Page 28: NPCA summer 2014

DFS

1

3

4 65

2

Page 29: NPCA summer 2014

DFS

1

3

4

7

65

2

Page 30: NPCA summer 2014

DFS

1

3

4

7

8

65

2

Page 31: NPCA summer 2014

DFS

1

3

4

7

8

965

2

Page 32: NPCA summer 2014

DFS

1

3

4

7

8

965

2

Page 33: NPCA summer 2014

DFS

1

3

4

7

8

965

2

Page 34: NPCA summer 2014

DFS

1

3

4

7

8

9

10

65

2

Page 35: NPCA summer 2014

DFS

1

3

4

7

8

119

10

65

2

Page 36: NPCA summer 2014

DFS

1

3

4

7

8

119

10

65

2

Page 37: NPCA summer 2014

DFS

1

3

4

7

8

119

10

65

2

Page 38: NPCA summer 2014

DFS

1

3

4

7

8

119

10

65

2

Page 39: NPCA summer 2014

DFS

1

3

4

7

8

12

119

10

65

2

Page 40: NPCA summer 2014

DFS

1

133

4

7

8

12

119

10

65

2

Page 41: NPCA summer 2014

DFS

1

14

133

4

7

8

12

119

10

65

2

Page 42: NPCA summer 2014

DFS

1

14

133

4

7

8

12

119

10

65

2

Page 43: NPCA summer 2014

DFS

1

14

133

4

7

8

12

15119

10

65

2

Page 44: NPCA summer 2014

DFS

1

14

133

4

7

8

12

15119

10

65

2

Page 45: NPCA summer 2014

DFS

1

14

133

4

7

8

12

15119

10

65

2

Page 46: NPCA summer 2014

DFS

1

14

133

4

7

8

12

15119

10

65

2

Page 47: NPCA summer 2014

DFS

1

14

133

4

7

8

12

15119

10

65

2

Page 48: NPCA summer 2014

DFS

● 行けるところまで行く

– それ以上いけなくなった戻る● どうやって実装するの?

– スタックを用いる

– 関数の再帰を用いる

Page 49: NPCA summer 2014

スタックってなんやねん

● データ構造の一つ● pushとpopという操作がある

– push ‥‥ スタックの一番上に積む

– pop ‥‥ スタックの一番上から取り出す● できることはこれだけ

Page 50: NPCA summer 2014

スタック(stack)

Page 51: NPCA summer 2014

DFS

1

1

  スタック

1をpush

Page 52: NPCA summer 2014

DFS

1

2

1

  スタック

2をpush

2

Page 53: NPCA summer 2014

DFS

1

3

2

1

  スタック

3をpush

2

3

Page 54: NPCA summer 2014

DFS

1

3

4

2

1

  スタック

4をpush

2

3

4

Page 55: NPCA summer 2014

DFS

1

3

4

2

1

  スタック

もうこれ以上進めない

2

3

4

Page 56: NPCA summer 2014

DFS

1

3

4

2

1

  スタック

4をpop

2

3

スタックの一番上が今見ている状態

Page 57: NPCA summer 2014

DFS

1

3

4 5

2

1

  スタック

5をpush

2

3

スタックの一番上が今見ている状態

5

Page 58: NPCA summer 2014

● というようにスタックが空になるまで続ける● 関数の再帰を使えばもっと簡単に実装できる

– 関数の再帰はスタックを用いて実現されている

● 進めなくなってpopするのが関数でreturnするのに相当

Page 59: NPCA summer 2014

関数が再帰する様子

● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

int main(){

dfs(1);

return 0;

}

Page 60: NPCA summer 2014

関数が再帰する様子

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

retrun;

}

Page 61: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

retrun;

}

Page 62: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

Page 63: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

dfs(4)

Page 64: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

dfs(4)

Page 65: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

dfs(5)

Page 66: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

dfs(5)

Page 67: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

Page 68: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(4)

Page 69: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(4)

Page 70: NPCA summer 2014

関数が再帰する様子

dfs(2)

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

Page 71: NPCA summer 2014

関数が再帰する様子

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

Page 72: NPCA summer 2014

関数が再帰する様子

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

Page 73: NPCA summer 2014

関数が再帰する様子

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

dfs(4)

Page 74: NPCA summer 2014

関数が再帰する様子

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

Page 75: NPCA summer 2014

関数が再帰する様子

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

dfs(5)

Page 76: NPCA summer 2014

関数が再帰する様子

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

dfs(3)

Page 77: NPCA summer 2014

関数が再帰する様子

dfs(1)● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

Page 78: NPCA summer 2014

関数が再帰する様子

● void dfs(int x){

if(x>=4)return;

dfs(x+1);

dfs(x+2);

return;

}

Page 79: NPCA summer 2014

関数が再帰する様子

dfs(3)

dfs(2)

dfs(3)

dfs(4)

dfs(5)

dfs(4)

dfs(1)

dfs(4)

dfs(5)

Page 80: NPCA summer 2014

DFS

● 関数の再帰でDFSが実現されている様子が

わかりましたか?

● AさんとBさんで数字を言い合うゲームをする

– 最初の人が1を言う

– 交互に前の人が言った数字+1または+2を言う

– 4以上を言ったほうが負け

Page 81: NPCA summer 2014

DFS

dfs(3)

dfs(2)

dfs(3)

dfs(4)

dfs(5)

dfs(4)

dfs(1)

dfs(4)

dfs(5)

A B A B

Bさんが3を言えば必勝

Page 82: NPCA summer 2014

DFS

● 全探索するといろんなことがわかります● 自分がある手を選択したときの勝率など

Page 83: NPCA summer 2014

DFS

● dfs(今の状態){

for each(今の状態から行ける状態):dfs(次の状態)

return

}

● 状態を引数に与えてやる● 戻り値は調べたい事柄によって様々

Page 84: NPCA summer 2014

実際にDFSしてみよう!

● 問題

品物がN個あり、値段はそれぞれC[i]円です。

NPCA君はなるべくM円に近い買い物をしたいです。

M円との差額は何円に抑えられるでしょう。

制約 1 N 20≦ ≦

1 C[i] 10^3≦ ≦

1 M 10^5≦ ≦

Page 85: NPCA summer 2014

状態の表し方

● まずは状態の表現の仕方を考えてみる

– 表し方が複雑だと計算量が増えることもある● なるべくシンプルにかつすべての場合を尽くせるように

– 引数の個数が多くならないように

– 区別の必要な複数の状態が同じように表されてはだめ

Page 86: NPCA summer 2014

区別が必要

● 品物を1~Nと番号付ける● 品物1,2を買うのと、品物2,1を買うのは区別が必要か?

– 今回注目しているのは合計金額なので

– 買う順番には興味がない

→ 1番の品物から順に買うかどうか決めていく事にする

Page 87: NPCA summer 2014

必要な情報

● では買った品物のリストをもっておけばいい?

→ 情報の持ちすぎ、合計金額に興味があるので

  それだけをもっていればいい

● 必要な状態は

(今何番目の品物まで見たか,今までに買った金額)

Page 88: NPCA summer 2014

DFS

int ans=INF;

void dfs(int x,int sum){

if(x==N+1){

if(ans>abs(M-sum))ans=abs(M-sum);

return;

}

dfs(x+1,sum+C[x+1]);

dfs(x+1,sum);

return;

}

int main(){

dfs(1,0);

return 0;

}

Page 89: NPCA summer 2014

DFS

int ans=INF; // INFはとても大きな値(具体的には10^9くらい)

void dfs(int x,int sum){ // x 何番目か sum 今まで買った合計金額

if(x==N+1){   //最後のN番目の品物まで見終わった

if(ans>abs(M-sum))ans=abs(M-sum); //absは絶対値

return;

}  この部分を終了条件という

dfs(x+1,sum+C[x]); //品物 x を買う場合

dfs(x+1,sum);    //買わない場合

return;

}

Page 90: NPCA summer 2014

DFS

int main(){

dfs(1,0);

return 0;

}

● Main関数の方から呼び出すときは、

1番目を見る、まだ何も買っていないので合計金額は0

だからdfs(1,0);

Page 91: NPCA summer 2014

DFS

● 終了条件を書かないと再帰がいつまでも続いて、

配列外参照、スタックオーバーフローなどを起こす● 今回はN個目まですべて買うか買わないか決めたあと、

x=N+1となったところで終了させた

Page 92: NPCA summer 2014

実際に解いてみよう

● PKU 3628 Bookshelf 2

● 問題概要● N匹の牛と高さBの本棚がある● 各牛の高さはH[i]

● 牛たちは積み上がって本棚の高さ以上に届きたい● あまり高すぎるとあぶないのでなるべく低いほうがいい● そのような時の本棚の高さとの差はいくらか

Page 93: NPCA summer 2014

解けましたか?

● さっきの問題とかなり似てますね● この問題でも牛の順序はどうでもいいので● (何番目の牛まで見たか,今まで積んだ合計の高さ)

● があればいいです● 終了条件は(何番目の牛まで見たか=N+1)ですね

Page 94: NPCA summer 2014

実際に解いてみよう

● NPCA Judge #99 講義用問題2

Page 95: NPCA summer 2014

解けましたか?

● まず状態を表すのに必要な要素を考えてみよう● 欲しい情報

– 順番は決まっているので左から見ていくことにする

– 今の所連続して表を向いているところの和

(前に切れてしまったところはどうでもいい)

– 裏がえす枚数に制限がある● (今何番目まで見たか,今連続して表になってる所の和,裏返した枚数)

Page 96: NPCA summer 2014

● 終了条件以外にも、こういう状態になったらもうダメ

というのがあればそこで探索を打ち切ってしまうと

高速化につながります

– 枝刈りといいます

● 再帰関数の挙動はわかりにくいかもしれませんが

問題を解いていくと慣れると思います

Page 97: NPCA summer 2014

BFS

1

Page 98: NPCA summer 2014

BFS

1

2

Page 99: NPCA summer 2014

BFS

1

32

Page 100: NPCA summer 2014

BFS

1

3 42

Page 101: NPCA summer 2014

BFS

1

5

3 42

Page 102: NPCA summer 2014

BFS

1

5

3

6

42

Page 103: NPCA summer 2014

BFS

1

5

3

6

4

7

2

Page 104: NPCA summer 2014

BFS

1

85

3

6

4

7

2

Page 105: NPCA summer 2014

BFS

1

85

9

3

6

4

7

2

Page 106: NPCA summer 2014

BFS

1

85

9

3

6

4

7

10

2

Page 107: NPCA summer 2014

BFS

1

85

9

3

6

4

7

1110

2

Page 108: NPCA summer 2014

BFS

1

85

9

3

6

4

12

7

1110

2

Page 109: NPCA summer 2014

BFS

1

85

9

3

6

4

1312

7

1110

2

Page 110: NPCA summer 2014

BFS

1

14

85

9

3

6

4

1312

7

1110

2

Page 111: NPCA summer 2014

BFS

1

14

85

9

3

6

4

151312

7

1110

2

Page 112: NPCA summer 2014

BFS

● 近い所から順に探索● どうやって実装するの?

– キューを用いる

Page 113: NPCA summer 2014

キューってなんやねん

● データ構造の一つ● pushとpopという操作がある

– push ‥‥キューの一番上に積む

– pop ‥‥キューの一番下から取り出す● できることはこれだけ

Page 114: NPCA summer 2014

キュー(queue)

55

11

push 5 push 11 pop push 2 push6 pop pop

11

5

1111

2

11

2

6

2

6

11

2

6

5 11

6

2

Page 115: NPCA summer 2014

キュー

● 今回は再帰みたいな代わりがないからキューを実装する

必要がある● どないすんねん

Page 116: NPCA summer 2014

キュー

● ほとんどの言語にはstack,queueなどのライブラリが存在

する(はず)

● C++ならstackヘッダ、queueヘッダに入っている

Page 117: NPCA summer 2014

キュー

● キューの使い方(C++)

– queue<T> hoge;   Tは型

– hoge.push(x); hogeにxをpush

– hoge.pop();     hogeからpop

– hoge.front();     hogeから次にpopされる値

Page 118: NPCA summer 2014

BFS

1

1

  キュー

最初に1をpushしておく

Page 119: NPCA summer 2014

BFS

1

  キュー

1をpop

Page 120: NPCA summer 2014

BFS

1

2

2

  キュー

2をpush

Page 121: NPCA summer 2014

BFS

1

32

2

  キュー

3をpush

3

Page 122: NPCA summer 2014

BFS

1

3 42

2

  キュー

4をpush

3

4

Page 123: NPCA summer 2014

BFS

1

3 42

2

  キュー

ここまでが1をpopしたあとの処理

3

4

Page 124: NPCA summer 2014

BFS

1

3 42

3

  キュー

2をpop

4

Page 125: NPCA summer 2014

BFS

1

5

3 42

3

  キュー

5をpush

4

5

Page 126: NPCA summer 2014

BFS

1

5

3 42

3

  キュー

ここまでが2をpopした後の処理

4

5

Page 127: NPCA summer 2014

BFS

1

5

3 42

4

  キュー

3をpop

5

Page 128: NPCA summer 2014

BFS

1

5

3

6

42

4

  キュー

6をpush

5

6

Page 129: NPCA summer 2014

BFS

● こんな感じでキューが空になるまでやる

– キューが空になったときすべて調べつくされている● 実装どないすんねん

Page 130: NPCA summer 2014

BFS

● void bfs(){

queue<状態の型> q;

q.push(はじめの状態);

while(!q.empty()){

(状態) cur = q.front();

q.pop();

for each(今の状態から行ける状態){

if(今まで訪れてない)q.push(次の状態);

    }

}

return;

}

Page 131: NPCA summer 2014

DFS,BFSにおける注意

● どちらでも同じところをなんども探索するのは無駄

– 配列にその状態を訪れたかどうか記憶しておく

– 前のソースコードでは状態をマークするのは

省略しているので注意● 配列に値を記憶しながらDFS → メモ化再帰

Page 132: NPCA summer 2014

全探索まとめ

● 大きく分けてDFS,BFSの2種類がある● それぞれstack,queueを用いて実装できる● 状態の表し方はなるべくシンプルに

– 興味のない情報は要らない● あとは慣れ

Page 133: NPCA summer 2014

  DP

Page 134: NPCA summer 2014

DPってなんやねん

● 動的計画法(Dynamic Programming)の略● だから動的計画法ってなんやねん

Page 135: NPCA summer 2014

DPってなんやねん

● 現時点では全探索の無駄を省いたものというような

認識でOK

● 百聞は一見に如かずじゃ

Page 136: NPCA summer 2014

● 問題

品物がN個あります。

各品物には重さW[i]kgと価値V[i]円があります。

NPCAくんは重いものを運ぶのが苦手なのでM[kg]までしか持ちたくないです。

なるべく価値の総和が高くなるように品物を選ぶ時価値の総和はいくらになるでしょう。

● 制約 1 N 100 1 W,V 100 1 M 10000≦ ≦ ≦ ≦ ≦ ≦

Page 137: NPCA summer 2014

● ナップサック問題と呼ばれる超有名問題

● DPの紹介のときに必ずといってもいいほど登場する

● JOIでは食事のときに手で解かないといけない

Page 138: NPCA summer 2014

● とりあえずさっきやったDFSで全探索してみよう● 状態の表し方を考えてみる● この場合も品物を選ぶ順番は関係ない● 制約があるのは重さなので今までに選んだ品物の重さの総和が必要

● 価値の最大値を戻り値で返すことにする● (何番目の品物まで見たか,選んだ品物の重さの総和)

Page 139: NPCA summer 2014

● int dfs(int x,int sum){

if(x==N+1)return 0;

if(sum+W[x]>M)return dfs(x+1,sum);

return max(dfs(x+1,sum),dfs(x+1,sum+W[x])+V[x]);

}

int main(){

printf(“%d\n”,dfs(1,0));

return 0;

}

Page 140: NPCA summer 2014

● 解けた、やったね● N 20≦ 程度なら通る

– 2^Nの状態を調べている● しかしN 100≦

● どこに無駄があるんだろう?

Page 141: NPCA summer 2014

● (例)

● N=5,M=10

● (W,V)=(1,1),(2,4),(3,2),(3,5),(4,7)

Page 142: NPCA summer 2014

● (例)

● N=5,M=10

● (W,V)=(1,1),(2,4),(3,2),(3,5),(4,7)

● 品物1,2をとって今4番目の品物を見ている時dfs(4,3)

Page 143: NPCA summer 2014

● (例)

● N=5,M=10

● (W,V)=(1,1),(2,4),(3,2),(3,5),(4,7)

● 品物1,2をとって今4番目の品物を見ている時 dfs(4,3)

● 品物3をとって今4番目の品物を見ている時 dfs(4,3)

● 同じものが複数回呼ばれている。

Page 144: NPCA summer 2014

● 関数は引数が同じであれば帰ってくる値はもちろん同じ● 1回呼んだら配列に記憶しておく

– 2回目以降再帰せずにO(1)で値が帰ってくる● 引数のパターンはxが1~N+1,sumが0~1000

– (Nの上限)*1000=10^5程度

Page 145: NPCA summer 2014

改良版DFS

● int memo[100][1010];

int dfs(int x,int sum){

if(memo[x][sum]!=-1)return memo[x][sum];

if(x==N+1)return 0;

if(sum+W[x]>M)return memo[x][sum]=dfs(x+1,sum);

return memo[x][sum]=max(dfs(x+1,sum),dfs(x+1,sum+W[x])+V[x]);

}

int main(){

memset(memo,-1,sizeof(memo));//memoの全要素を-1で初期化 printf(“%d\n”,dfs(1,0));

return 0;

}

Page 146: NPCA summer 2014

● このように配列に値を記憶しておいて無駄な再帰を防ぐのをメモ化再帰という

● DPは再帰を行わずにこれを解く

Page 147: NPCA summer 2014

● 突然だが次のような配列を考える● dp[i][j]:=i番目までの品物から重さj以下になるように品物

を選んだ時の価値の総和の最大値● 今求めたいのはdp[N][M]

● DPではこの配列を埋めていくことで答えを求める● ではどのように埋めていくのか?

Page 148: NPCA summer 2014

● とりあえず確定する場所がある

– dp[0][0]=0

– 0番目の品物というのは存在しないがとりあえず何も

品物を選んでない状態。当然価値,重さの合計は0。● ここから、配列dpの要素同士の関係を利用して配列を

埋めていく● 逆に、関係性がなければDPはできない。

Page 149: NPCA summer 2014

● では今回の場合どのような関係があるのか?

● dp[i][j]:=i番目までの品物から重さj以下になるように品物 を選んだ時の価値の総和の最大値

● i番目の品物を選んだ場合、選ばなかった場合のどちらか

– 排中律

Page 150: NPCA summer 2014

● i番目の品物を選んだ場合が最大の時

dp[ i ][ j ] = dp[ i-1 ][ j - W[i] ]+V[i]

– i-1番目までの品物からj-W[i]以下の重さの品物を選んだ時の価値の総和の最大値にi番目の品物の価値が加わる

● i番目の品物を選ばない場合が最大の時

dp[ i ][ j ] = dp[ i-1 ][ j ]

– i-1番目までの品物からj以下の重さの品物を選んだ時の価値の総和の最大値と同じ

Page 151: NPCA summer 2014

● dp[i][j]はこれらのどちらか大きい方● dp[i][j]=max(dp[i-1][j],dp[i-1][j-W[i]]+V[i])

● これが関係式● この式をみてわかるようにi,jが小さいものから順に

確定していく● dp[0][0]は0だとわかっている

Page 152: NPCA summer 2014

● int dp[105][1010];

int main(){

for(int i=1;i<=N;i++){

for(int j=0;j<=M;j++){

if(j-W[i]<0)dp[i][j]=dp[i-1][j];

else dp[i][j]=max(dp[i-1][j],dp[i-1][j-W[i]]+V[i]);

}

}

return 0;

}

Page 153: NPCA summer 2014

● int dp[105][1010];

int main(){

for(int i=1;i<=N;i++){

for(int j=0;j<=M;j++){

if(j-W[i]<0)dp[i][j]=dp[i-1][j];

else dp[i][j]=max(dp[i-1][j],dp[i-1][j-W[i]]+V[i]);

}

}

return 0;

}

Page 154: NPCA summer 2014

● このように添字が負にならないように注意● この場合は j < W[i]となる場合を考えなくてよい

–重さW[i]の品物を買って重さの合計がjとなることはない

● もし添字が負になるような状況も考えなければならないなら適宜げたをはかせて添字を非負にしてやる

– std::mapとかでやってもいいかも

Page 155: NPCA summer 2014

メモリ節約

● さっきのソースコードを見てわかるとおり

dp[i][ ]はdp[i-1][ ]の影響しか受けない● 2つ以上前は覚えておく必要がないので捨てていく

Page 156: NPCA summer 2014

● int dp[2][1010];

int main(){

for(int i=1;i<=N;i++){

for(int j=0;j<=M;j++){

if(j-W[i]<0)dp[i%2][j]=dp[(i-1)%2][j];

else dp[i%2][j]=max(dp[(i-1)%2][j],dp[(i-1)%2][j-W[i]]+V[i]);

}

}

return 0;

}

● 今回は必要なかったが必ず必要なときもある

Page 157: NPCA summer 2014

● 自分で解く時どうするの?

● 今日は突然● dp[i][j]:=i番目までの品物から重さj以下になるように品物

を選んだ時の価値の総和の最大値● が与えられたからできた● 自分で思いつくしかない● どないしたらええねん

Page 158: NPCA summer 2014

● メモ化再帰の時と同様に、状態を表すのに必要な最小限の要素を考える

Page 159: NPCA summer 2014

● メモ化再帰の時と同様に、状態を表すのに必要な最小限の要素を考える

● 最初から無理しなくてよい。徐々に要らない情報を省いていくと解けることは多い

● 関係式の作り方によっては計算量が大きく変わる

Page 160: NPCA summer 2014

● あとは慣れなので問題を解くのが大事

– 何事も精進だね、うん● よく出てくる形がいくつかある

Page 161: NPCA summer 2014

よくあるかたち

● i番目までの から〜 〜

– 今回のナップサック問題も。非常に多い

● 長さ/大きさ i の をつくる時の〜 〜(残せる) の最小〜 /最大

Page 162: NPCA summer 2014

● それっぽいdpテーブルを思いついても

関係式(漸化式)がわからないととけない● これもやっぱり経験

– 何事も精進だね、うん● こちらもよく出てくる形がある

Page 163: NPCA summer 2014

よくあるかたち

● dp[i]とdp[i-1]の関係に注目するもの

– かなり多い

● min(dp[j],dp[j- ]+▲,dp[j- *2]+▲*2 dp[j- *k]+▲*k)◯ ◯ ‥‥ ◯

– 比較的よくある

– この形の場合計算量を落とすテクがある

– 今日は話しませんが興味があったら蟻本を読むか

私に聞いてください

Page 164: NPCA summer 2014

実際に解いてみよう

● PKU 2385 Apple Catching

● 2本の林檎の木がある● 初めBessieは木1にいる● 一定の間隔でT回どちらか

から林檎が落ちてくる● W回以下しか移動したくない● 最大でいくつの林檎を取れるか● 制約 1 T 1000 1 W 30≦ ≦ ≦ ≦

Page 165: NPCA summer 2014

解けましたか?

● dp[i][j]:=i回目の林檎の落下までで移動回数j回以下で

取れる最大の個数● どちらの木にいるのかという情報がない?

– 移動回数からすぐにわかる

– 移動回数が奇数なら木2,偶数なら木1

Page 166: NPCA summer 2014

● 漸化式を考えてみよう● i回目の落下が今居る木の時 dp[i][j]=dp[i-1][j]+1

● i回目の落下が今居る木でない時

– 取りに行く時 dp[i-1][j-1]+1

– 取りに行かない時 dp[i-1][j]

– dp[i][j]=max(dp[i-1][j-1]+1,dp[i-1][j])

● 初期条件 dp[0][0]=0

Page 167: NPCA summer 2014

● int dp[1010][35];

int main(){

for(int i=1;i<=T;i++){

for(int j=0;j<=W;j++){

if((j%2==0&&fall[i]==1)||(j%2==1&&fall[i]==2))dp[i][j]=dp[i-1][j]+1;

else dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]+1);

}

}

return 0;

}

Page 168: NPCA summer 2014

メモ化再帰でも解けます

● int rec(int x,int mov){

if(memo[x][mov]!=-1)return memo[x][mov];

if((mov%2==1&&fall[i]==1)||(mov%2==0&&fall[i]==2)){

return memo[x][mov]=rec(x-1,mov)+1;

}

return memo[x][mov]=max(rec(x-1,mov-1)+1,rec(x-1,mov));

}

Page 169: NPCA summer 2014

DP

● DPの雰囲気はつかんでいただけたでしょうか● DPは本当に解いた量がものをいうと思います

– つらい● がんばろう

Page 170: NPCA summer 2014

DPの勉強方法

● 何もなしでいきなり解くのは難しいかもしれない

● 蟻本を読もう

– 持ってなくても部室に2冊あります

● 蟻本で感覚をつかんだらAOJやPKUの問題を解こう

Page 171: NPCA summer 2014

DPの大切さ

● 競技プログラミングではなくても探索やDPなどが必要に

なることは多々あると思います● JOIの本選,春合宿へ行けるかどうかに関わります

– (去年本選で私と部長はDPをこじらせて死んだ)

● 上のようにdpテーブル,漸化式があってても意外とバグる

– 初期化を適切にするのは意外と難しい● 結局経験なんです

– 何事も精進だね、うん

Page 172: NPCA summer 2014

DPの大切さ

● JOI予選突破を目指すならばDPが重要

– 予選4番はほぼDP,ボーダーは300〜400点● 頑張ろうな● この後のプロコンはJOI予選対策という位置づけなので

DPを一問以上入れてます。● 今練習して解けるようになっちゃいましょう!

Page 173: NPCA summer 2014

Let's 実装

Page 174: NPCA summer 2014

~問題演習~

● AOJ 0573 Night Market

● PKU 2229 Sumsets

● PKU 2465 Adventures in Moving - Part IV

● PKU 3046 Ant Counting

● PKU 3616 Milking Time

● PKU 3280 Cheapest Palindrome

● AOJ 0550 Dividing Snacks

● 0573だけ解説するので暇な人は後のもやっといて● 5分ごとくらいにヒント開示します

Page 175: NPCA summer 2014

● ヒント1

夜店を訪れる順番は訪れる店を決めると一意に定まる

→ i番目までの~から

Page 176: NPCA summer 2014

● ヒント2

状態の表し方

(今何番目の店まで見たか,今の時刻)

→ dp[i][j]:=i番目の店までで時刻jまでに得られる楽しさのMax

Page 177: NPCA summer 2014

● ヒント3

漸化式

とりあえず任意のi,jでdp[i][j]はdp[i][j-1]以上

店iで遊ぶ時,花火の時間とかぶっていれば遊べない

j-B[i] ~ jまで遊ぶのでj-B[i]<S&&S<jならば

i-1番目までの店で時刻jまでのMaxと同じ

dp[i][j]=max(dp[i][j-1],dp[i-1][j])

Page 178: NPCA summer 2014

● ヒント4

それ以外の時は遊ぶか遊ばないか選べる

遊ぶ時 i-1番目の店まででj-B[i]まで遊んで,

j-B[i]~jの間店iで遊ぶ → dp[i-1][j-B[i]]+A[i]

遊ばない時 i-1番目の店まででjまで遊ぶ

→ dp[i-1][j]

これらの大きい方

dp[i][j]=max(dp[i][j-1],dp[i-1][j],dp[i-1][j-B[i]]+A[i])

Page 179: NPCA summer 2014

● 答え

for(int i=1;i<=N;i++){

for(int j=0;j<=M;j++){

if(j-B[i]<0)dp[i][j]=max(dp[i-1][j],dp[i][j-1]);

if(j-B[i]<S&&S<j)dp[i][j]=max(dp[i-1][j],dp[i][j-1]);

else dp[i][j]=max(dp[i][j-1],dp[i-1][j],dp[i-1][j-B[i]]+A[i]);

}

} 添字が負になる時に注意

Page 180: NPCA summer 2014

お疲れ様でした