競技プログラミング練習会2015 Normal 第1回

111
2015/04/17 長嶺英朗(ID:hnagamin) 競技プログラミング練習会 2015 Normal 1

Transcript of 競技プログラミング練習会2015 Normal 第1回

2015/04/17長嶺英朗(ID:hnagamin)

競技プログラミング練習会2015 Normal

第1回

目次

● 計算量● ランダウの記号● ソートアルゴリズム● 動的計画法

計算量

アルゴリズムと計算資源

● アルゴリズムを実行するのに必要なリソース(メモリとか時間とか)は入力によって変わってくる

– 例えば「nを素因数分解せよ」という問題を解くアルゴリズムを考えた場合、nが大きくなるほど計算に時間がかかりそう

● 入力に応じて必要な計算資源の量がどう変化するかを考察したい– プログラムへの入力を引数にとって実行に必要な計算資源

の量を返す関数を考えよう

計算量

● アルゴリズムを実行する時に必要になる計算資源の量– 時間計算量

● 実行に必要な時間の長さ– 空間計算量

● 実行に必要な記憶領域の大きさ– もっと詳しくアルゴリズムを分析する時には最悪時間計算量

とか平均時間計算量とか色々な計算量を考えます● 「時間計算量が小さいアルゴリズム」は「時間効率の良

いアルゴリズム」、みたいなイメージ● 普通計算量と言えば時間計算量を指す

ランダウの記号

● 計算量を考えるときに便利な記法● 入力サイズが大きい時の計算量がわかりやすい

O(n2)

ランダウの記号

ある計算量がO(n2)であるとは、

その計算量を表す関数f(n)に対して

であること。

∃N ,∃ k ;n>N ⇒ f (n)<k n2

ランダウの記号

ある計算量がO(n2)であるとは、

その計算量を表す関数f(n)に対して

であること。

→「nが大きいときは、f(n)はn2より速いペースで大きくなっていったりしない」的な意味

∃N ,∃ k ;n>N ⇒ f (n)<k n2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 210

500

1000

1500

2000

2500

3000

入力

計算

計算量のイメージ

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 210

500

1000

1500

2000

2500

3000

入力

計算

計算量のイメージn2

kn2

f(n)

ランダウの記号

● 定数倍は無視する– O(2n) = O(n)

– O(log2 n) = O(log10 n)

● 小さい項は無視する– O(1000 + 50n + n²) = O(n²)

– O(n + log n) = O(n)

– O(en + n100) = O(en)

競技プログラミングとランダウの記号

● nに具体的な値を代入して制限時間に間に合うか確かめることが多い– 1秒間にできる処理は多くて1億回くらい

– 例: 「計算量がO(MN)でM 5000, N 100≦ ≦ だから十分間に合う」

ソートアルゴリズム

ソートと計算量

● ソートを行うアルゴリズムを考えます– ソートは、数のリストを入力にとって小さい順に並べ替え

たリストを返す問題のこと● 比較回数と交換回数を計算量とすることが多い● 計算量のパラメータはリストの長さn

– 長いリストほど並べ替えに時間がかかる● 効率がいいやつと悪いやつがある

ボゴソート

● リストを適当にシャッフルして小さい順に並んでいるか確かめる

● 上の操作を小さい順に並ぶまで繰り返す

● 並べ方は高々n!通りしかないので、毎回異なる並べ方をするようにしておけばO(n×n!)でソートできる

バブルソート

● 隣接する要素を見比べて小さい順に並んでいればそのまま、逆に並んでいれば入れ替えるという操作を繰り返す

バブルソートの動作

853 674 2 1

バブルソートの動作

853 674 2 1

入れ替える

バブルソートの動作

854 673 2 1

バブルソートの動作

854 673 2 1

入れ替えない

バブルソートの動作

854 673 2 1

バブルソートの動作

854 673 2 1

入れ替える

バブルソートの動作

854 763 2 1

入れ替える

バブルソートの動作

874 563 2 1

入れ替える

バブルソートの動作

824 563 7 1

入れ替える

バブルソートの動作

824 563 1 7

入れ替えない

バブルソートの動作

824 563 1 7

バブルソートの動作

824 563 1 7

バブルソートの動作

824 563 1 7

バブルソートの動作

824 653 1 7

バブルソートの動作

864 253 1 7

バブルソートの動作

814 253 6 7

バブルソートの動作

814 253 6 7

バブルソートの動作

814 253 6 7

バブルソートの動作

854 123 6 7

バブルソートの動作

852 413 6 7

バブルソートの動作

851 432 6 7

バブルソートの動作

852 431 6 7

バブルソートの計算量

● 平均時間計算量O(n2)– (n-1)回の比較をn回行うとソートできるので

O( n2 -n ) = O(n2)● 効率が悪い

マージソート

● 2つのソートされたリストを併合して1つのソートされたリストにすることをマージという

● マージを使ってソートするのがマージソート

マージソート

● 2つのソートされたリストを併合して1つのソートされたリストにすることをマージという

● マージを使ってソートするのがマージソート

2 4 7 8 1 5 6 9

マージソート

● 2つのソートされたリストを併合して1つのソートされたリストにすることをマージという

● マージを使ってソートするのがマージソート

2 4 7 8 1 5 6 9

1 2 4 5 6 7 8 9

マージソート

● 2つのソートされたリストを併合して1つのソートされたリストにすることをマージという

● マージを使ってソートするのがマージソート● リストの長さがm, nのとき計算量はO(m + n)

– 2つのリストの先頭を見比べて、小さい方を取っていく

マージソート

1. リストを半分に分けて2つの部分リストを作る

2. 各部分リストをマージソートする

3. 2つの部分リストをマージする

マージソートの動作

854 673 2 1

マージソートの動作

854 673 2 1

マージソートの動作

85 2 14 673

マージソートの動作

85 2 167

43

マージソートの動作

85 2 14 673

マージソートの動作

85 2 143

67

マージソートの動作

85 2 14 763

マージソートの動作

854 763 2 1

マージソートの動作

4 76385 2 1

マージソートの動作

4 76381

5 2

マージソートの動作

4 76382 5 1

マージソートの動作

4 7632 5

81

マージソートの動作

4 76382 5 1

マージソートの動作

814 763 2 5

マージソートの動作

852 431 6 7

マージソートの計算量

● マージソートの計算量を表す関数をf(n)とすると、

f(2N+1) = f(2N) + f(2N) + 2×2N

が成り立つ。– 部分リスト(長さ2N)のマージソートにf(2N)

– 2リストのマージ(合計要素数2×2N)に2×2N

● f(2N) = 2N log2(2N)が分かる

● 従って、fはO(n log n)

– O(n2)よりちょっと速くなった

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 210

50

100

150

200

250

300

350

400

450

nlogn

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 210

50

100

150

200

250

300

350

400

450

10nlogn

メモ化再帰と動的計画法(DP)

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0042

A Thief

● お宝と、お宝を詰める風呂敷がある● お宝はそれぞれ重さと価値が決まっている● 風呂敷には合計重量Wまでお宝を詰められる● 上記の制限のもとで、風呂敷に詰めたお宝の価値

を最大にする– 実際にはそのときの重さも出力する必要がありますが、

この説明では省略します

A Thief

● お宝の数Nは1000以下

● 風呂敷の容量Wは1000以下

● 各お宝の価値は10000以下、重さは1000以下

全探索

● 各お宝に対して「風呂敷に詰める」か「詰めない」かを決めて、合計重量を計算する

● 重量をオーバーしていたら無視● オーバーしていなければ合計価値を計算し、最大

値と比較

全探索

● 各お宝に対して「風呂敷に詰める」か「詰めない」かを決めて、合計重量を計算する

● 重量をオーバーしていたら無視● オーバーしていなければ合計価値を計算し、最大

値と比較● 再帰関数で実装するとコード量が少ないので良い

– solve(i, w)を「i番目以降のお宝を容量wの風呂敷にできるだけ詰めたときの最大の価値」とする

実装

int solve(int i, int w) {if (i == N ­ 1) {

return (weight[i] > w ? 0 : value[i]);} else if (weight[i] > w) {

return solve(i + 1, w);} else {

return max(value[i] + solve(i + 1,w ­ weight[i]),solve(i + 1, w));

}} i 今注目している品物の番号

w 風呂敷の残り容量value[i] i番目のお宝の価値weight[i] i番目のお宝の重量

実装

int solve(int i, int w) {if (i == N ­ 1) {

return (weight[i] > w ? 0 : value[i]);} else if (weight[i] > w) {

return solve(i + 1, w);} else {

return max(value[i] + solve(i + 1,w ­ weight[i]),solve(i + 1, w));

}}

全探索

● 考えるべき「詰める」「詰めない」の組合せは2N通り

● これらを全て探索しているので、計算量はO(2N)

– solveの再帰が1段深くなる毎に必要な計算の量がだいたい2倍になっている

● Nは最大1000なので全然間に合わない

– 21000 > 10300

考察

● 無駄な計算をしていないか?● solveは同じ引数に対して同じ値を返す● 重量が同じ品物の組合せがあると、同じ計算が何

回も行われることになる

考察

● 無駄な計算をしていないか?● solveは同じ引数に対して同じ値を返す● 重量が同じ品物の組合せがあると、同じ計算が何

回も行われることになる

1weight

value 5

6 2 3 4 1 ・・・

3 2 4 7 1

i 0 1 2 3 4 5

・・・

・・・

考察

● 無駄な計算をしていないか?● solveは同じ引数に対して同じ値を返す● 重量が同じ品物の組合せがあると、同じ計算が何

回も行われることになる

1weight

value 5

6 2 3 4 1 ・・・

3 2 4 7 1

i 0 1 2 3 4 5

・・・

・・・

この後、solve(6, W-6)が計算される

考察

● 無駄な計算をしていないか?● solveは同じ引数に対して同じ値を返す● 重量が同じ品物の組合せがあると、同じ計算が何

回も行われることになる

1weight

value 5

6 2 3 4 1 ・・・

3 2 4 7 1

i 0 1 2 3 4 5

・・・

・・・

この後、solve(6, W-6)が計算される

考察

● 無駄な計算をしていないか?● solveは同じ引数に対して同じ値を返す● 重量が同じ品物の組合せがあると、同じ計算が何

回も行われることになる

1weight

value 5

6 2 3 4 1 ・・・

3 2 4 7 1

i 0 1 2 3 4 5

・・・

・・・

この後、solve(6, W-6)が計算される

メモ化再帰のアイデア

● 同じ引数に対する計算は2回以上しない● そのために、引数と計算結果を対応させるテーブル

を持っておく● C++だとstd::mapとか

– キーと値を対応させるデータ構造

実装

map<pair<int,int>, int> memo;

int solve(int i, int w) {  if (memo.find(make_pair(i, w)) != memo.end()) {    return memo[make_pair(i, w)];  } else if (i == N ­ 1) {    return (weight[i] > w ? 0 : value[i]);  } else if (weight[i] > w) {    memo[make_pair(i, w)] = solve(i + 1, w);     return memo[make_pair(i, w)];  } else {    int use = value[i] + solve(i + 1, w ­ weight[i]),        no_use = solve(i + 1, w);     memo[make_pair(i, w)] = max(use, no_use);    return memo[make_pair(i, w)];  }}

実装

map<pair<int,int>, int> memo;

int solve(int i, int w) {  if (memo.find(make_pair(i, w)) != memo.end()) {    return memo[make_pair(i, w)];  } else if (i == N ­ 1) {    return (weight[i] > w ? 0 : value[i]);  } else if (weight[i] > w) {    memo[make_pair(i, w)] = solve(i + 1, w);     return memo[make_pair(i, w)];  } else {    int use = value[i] + solve(i + 1, w ­ weight[i]),        no_use = solve(i + 1, w);     memo[make_pair(i, w)] = max(use, no_use);    return memo[make_pair(i, w)];  }}

保存されてたら計算しない

実装

map<pair<int,int>, int> memo;

int solve(int i, int w) {  if (memo.find(make_pair(i, w)) != memo.end()) {    return memo[make_pair(i, w)];  } else if (i == N ­ 1) {    return (weight[i] > w ? 0 : value[i]);  } else if (weight[i] > w) {    memo[make_pair(i, w)] = solve(i + 1, w);     return memo[make_pair(i, w)];  } else {    int use = value[i] + solve(i + 1, w ­ weight[i]),        no_use = solve(i + 1, w);     memo[make_pair(i, w)] = max(use, no_use);    return memo[make_pair(i, w)];  }}

計算したら保存する

メモ化再帰

● 各(i, w)に対する計算は高々1回しか行われない

● 計算すべき(i, w)の組合せは最大NW通り

● mapへアクセスするためにかかる時間はmapの要素数xに対してO(log x)– x NW≦ なのでO(log NW)

● 以上より、この解法の計算量はO(NW log NW)– N 1000, W 1000≦ ≦

– 間に合う

動的計画法

● よく考えると、わざわざ再帰的に書く必要はない● mapの代わりに2次元配列tableを用意して、

table[i][w]を「i番目以降のお宝を容量wの風呂敷にできるだけ詰めたときの最大の価値」とする

● table上で2重ループを使いtable[i][w]を計算する● 再帰しなくてもループで同じことを実現できる!!

実装

int solve() {  for (int i = N ­ 1; i >= 0; i­­) {    for (int w = 0; w <= W; w++) {      if (w >= weight[i]) {        table[i][w] = max(table[i+1][w],             value[i] + table[i+1][w­weight[i]]);      } else {        table[i][w] = table[i+1][w];      }    }  }  return table[0][W];} 

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3

4

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3

4 0

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3

4 0 0

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3

4 0 0 0

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3

4 0 0 0 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3

4 0 0 0 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3

4 0 0 0 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3 0

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3 0 0

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3 0 0 0

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3 0 0 0 10

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3 0 0 0 10 10

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3 0 0 0 10 10 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2 0

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2 0 0

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2 0 0 0

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2 0 0 0 120

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2 0 0 0 120 120

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2 0 0 0 120 120 210

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1

2 0 0 0 120 120 210 210

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0

1 0 0 100 120 120 220 220

2 0 0 0 120 120 210 210

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0 0 60 100 160 180 220 280

1 0 0 100 120 120 220 220

2 0 0 0 120 120 210 210

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

何がどうなっているんだ

6560,1100,2120,3210,410,4

i\w 0 1 2 3 4 5 6

0 0 60 100 160 180 220 280

1 0 0 100 120 120 220 220

2 0 0 0 120 120 210 210

3 0 0 0 10 10 210 210

4 0 0 0 10 10 10 10

i 0 1 2 3 4

weight 1 2 3 5 3

value 60 100 120 210 10

動的計画法

● ループの回数はnW回● ループ中の計算は定数時間● 従って、計算量はO(NW)

– N 1000, W 1000≦ ≦

– 間に合う

問題文の条件に注意しましょう

● 条件によってはメモ化再帰・動的計画法より全探索の方が高速になることがあります– N 10, W 10≦ ≦ 9という条件の下では

O(2N) : 103くらい

O(NW) :1010くらい

● 問題文と条件に合わせて適切なアルゴリズムを選んでください