paiza プログラミング

[Ruby]paiza DPメニュー 3項間漸化式 2: STEP5, FINAL

dpメニュー1-2

こんにちは!じゃいごテックのあつしです。

今回はpaiza DPメニュー 「セクション1【漸化式】 3項間漸化式 2- STEP5, FINAL」 を解説します。

この問題集は動的計画法(Dynamic Programming, DP)に関する6個のセクションで構成されています。
各セクションにはそれぞれ複数のSTEP問題FINAL問題があり、STEP問題を解いていけばFINAL問題も解けるはず!となっています。

STEP問題 5 を解いてみる

簡単な解説は付けていますが、難しいと感じたら下記の記事も参考にしてみて下さい。

STEP5: 3項間漸化式 1 (paizaランク B 相当)

STEP5 はフィボナッチ数列の第k項を出力する問題です。フィボナッチ数列は下記の漸化式で表される数列です。

漸化式

  • a_1 = 1(第1項)
  • a_2 = 1(第2項)
  • a_n = a_{n-2} + a_{n-1} (n ≧ 3)

解答例

INPUT1 = <<~"EOS"
  7
EOS
OUTPUT1 = <<~"EOS"
  13
EOS

def solve(input_data)
  k = input_data.to_i

  # dpテーブル初期化
  # n = 1 : 1
  # n = 2 : 1
  dp = [1, 1]

  # dpテーブル更新
  # n >= 3 : dp[n - 2] + dp[n - 1]
  (k - 2).times do |n|
    dp << dp[-2] + dp[-1]
  end

  # k 項の値を返す
  dp[k - 1]
end

puts solve(STDIN.read)

# 確認用コード
# puts solve(INPUT1)
# > 13
  • 整数kを受け取る。
  • dp[1, 1]を代入する。(dp[0]に第1項=1、 dp[1]に第2項=1が入る)
  • k-2回の繰り返し処理を設定する。
    (第2項までは求められているので、第3項を求めるには3-2=1回処理を行う)

    • dp末尾dp[-2]+dp[-1]を追加する。
  • dp[k-1]第k項の値が入っているので返す。
    (dp[0]: 1項の値, dp[1]: 2項の値, dp[2]: 3項の値, ..., dp[k-1]: k項の値)

FINAL問題: 3項間漸化式 2 (paizaランク B 相当)

※ paizaレベルアップ問題集 DPメニューより

問題

整数 Q と Q 個の整数 k_1, k_2, ... , k_Q が与えられます。

次のように定められた数列の k_i 項目の値を順に出力してください。

ちなみに、これはフィボナッチ数列と呼ばれる有名な数列です。

  • a_1 = 1
  • a_2 = 1
  • a_n = a_{n-2} + a_{n-1} (n ≧ 3)

入力される値

入力は以下のフォーマットで与えられます。

Q
k_1
k_2
...
k_Q

  • 1行目では、2行目以降で与えられる入力の行数 Q が与えられます。
  • 続く Q 行のうち i 行目では、k_i が与えられます。

入力値最終行の末尾に改行が1つ入ります。

期待する出力

Q 行出力してください。

i 行目には、数列の k_i 項目の値を出力してください。
また、末尾に改行を入れ、余計な文字、空行を含んではいけません。

a_{k_1}
a_{k_2}
...
a_{k_Q}

条件

すべてのテストケースにおいて、以下の条件をみたします。

  • 1 ≦ Q ≦ 100
  • 1 ≦ k_i ≦ 40 (1 ≦ i ≦ Q)
入力例1
5
1
2
3
4
3
出力例1
1
1
2
3
2
攻略ポイント

ポイント

  • 漸化式の通りに処理を行う。
問題を解く流れ

入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。

INPUT1 = <<~"EOS"
  5
  1
  2
  3
  4
  3
EOS
OUTPUT1 = <<~"EOS"
  1
  1
  2
  3
  2
EOS

# 確認用コード
p INPUT1
# > "5\n1\n2\n3\n4\n3\n"
p OUTPUT1
# > "1\n1\n2\n3\n2\n"

続いて、問題を解くsolveメソッドを変数の下に定義し、入力データを受け取る処理を書きます。

def solve(input_lines)
  q, *ary_k = input_lines.split("\n").map(&:to_i)

  # 確認用コード
  ary_k
end

# 確認用コード
p solve(INPUT1)
# > [1, 2, 3, 4, 3]

データが正しく受け取れていれば、solve メソッドに処理を追加していきます。

  • 整数qと続くq行の整数を配列ary_kに格納する。(整数qは今回は使ってない)
  • 配列ary_kの最大値を探してk_maxに代入する。
  • dp[1, 1]を代入する。(dp[0]に第1項=1、 dp[1]に第2項=1が入る)
  • k_max-2回の繰り返し処理を設定する。
    (第2項までは求められているので、第k_max項を求めるにはk_max-2回処理を行う)

    • dp末尾dp[-2]+dp[-1]を追加する。
  • ary_kの先頭から順に要素kを取り出してdp[k-1]を参照した値を配列で返す。
    (dp[0]: 1項の値, dp[1]: 2項の値, dp[2]: 3項の値, ..., dp[k-1]: k項の値)
def solve(input_lines)
  q, *ary_k = input_lines.split("\n").map(&:to_i)

  # dpテーブル初期化
  # n = 1 : 1
  # n = 2 : 1
  dp = [1, 1]

  # 最大の項
  k_max = ary_k.max

  # dpテーブル更新
  # n >= 3 : dp[n - 2] + dp[n - 1]
  (k_max - 2).times do |n|
    dp << dp[-2] + dp[-1]
  end

  # ary_k の先頭から順に k 項の値を返した結果を配列で返す
  ary_k.map { |k| dp[k - 1] }
end

# 確認用コード
p solve(INPUT1)
# > [1, 1, 2, 3, 2]
p solve(INPUT1) == OUTPUT1
# > false

あとは出力形式を整えれば完成です。

p solve(INPUT1) == OUTPUT1 -> true を確認するために、出力の配列を改行で結合して末尾に改行を加えます。

def solve(input_lines)
  q, *ary_k = input_lines.split("\n").map(&:to_i)

  # dpテーブル初期化
  # n = 1 : 1
  # n = 2 : 1
  dp = [1, 1]

  # 最大の項
  k_max = ary_k.max

  # dpテーブル更新
  # n >= 3 : dp[n - 2] + dp[n - 1]
  (k_max - 2).times do |n|
    dp << dp[-2] + dp[-1]
  end

  # ary_k の先頭から順に k 項の値を返した結果を改行区切りの配列にして末尾に改行を追加
  ary_k.map { |k| dp[k - 1] }.join("\n") << "\n"
end

# 確認用コード
p solve(INPUT1)
# > "1\n1\n2\n3\n2\n"
p solve(INPUT1) == OUTPUT1
# > true

入出力例での動作確認が出来ましたので、標準入力からのデータ受け取りに変更して、手動で動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read を使います。(入力終了は Ctrl+D 又は Ctrl+Z)

解答コード
def solve(input_lines)
  q, *ary_k = input_lines.split("\n").map(&:to_i)

  # dpテーブル初期化
  # n = 1 : 1
  # n = 2 : 1
  dp = [1, 1]

  # 最大の項
  k_max = ary_k.max

  # dpテーブル更新
  # n >= 3 : dp[n - 2] + dp[n - 1]
  (k_max - 2).times do |n|
    dp << dp[-2] + dp[-1]
  end

  # ary_k の先頭から順に k 項の値を返した結果を改行区切りの配列にして末尾に改行を追加
  ary_k.map { |k| dp[k - 1] }.join("\n") << "\n"
end

puts solve(STDIN.read)
(参考)トップダウン方式のDP
def fib(n)
  return [1] * n if n < 3

  dp = fib(n - 1)
  dp << dp[-1] + dp[-2]
end

def solve(input_data)
  q, *ary_k = input_data.split("\n").map(&:to_i)

  # 最大の項
  k_max = ary_k.max

  # 0 ~ k_max 項までの dpテーブル
  dp = fib(k_max)

  # ary_k の先頭から順に項の値を返す
  ary_k.map { |k| dp[k - 1] }
end

puts solve(STDIN.read)

# 確認用コード
# puts solve(INPUT1)
# > 1
# > 1
# > 2
# > 3
# > 2

今回のまとめ

セクション1では基本的な漸化式の処理を扱いました。

暗算出来るような簡単な問題でもDPに置き換えて考えてみると少し難しく感じたのではないでしょうか?
繰り返し中のdpを出力してみたり、ノートに書いてみるのも良いかもしれませんね。



【PR】アルゴリズム学習でお世話になった本


アルゴリズム関連の技術書は大抵C/C++で書かれていますが、ある程度プログラムが出来れば、処理の流れは理解することが出来ます。






通称: 螺旋本。C++で解説されています。AOJ(Aizu Online Judge)を運営している会津大学の渡辺准教授が書いた本です。データ構造や計算量の内容から丁寧に書いてありますのでアルゴリズム学習の最初の参考書としてオススメです。







通称: 蟻本。C++で解説されています。競技プログラミング中級者の定番書と言われていて、競技プログラミングで利用できる色々なアルゴリズムを学ぶことが出来ます。かなり高度な内容も含まれているので1冊分を完全に理解するのにはかなりの時間がかかりそうですが、手元に置いて何度も読み返しています。







通称: チーター本。C#, C++, JAVAでTopcoderの問題を解説しています。初心者~中級者向けに書かれているので解説が非常に丁寧です。







Python3でアルゴリズムを解説している本です。講義スタイルの本で、図やフローチャートを使ってアルゴリズムとデータ構造についてしっかりと説明されています。代わりにコードは少なめです。Pythonコードが読めなくても十分理解できると思います。


-paiza, プログラミング
-, ,

© 2024 じゃいごテック