Pythonでサイコロの出目の合計がターゲットと一致する組み合わせの数を求める
d個のサイコロがあり、それぞれのサイコロには1からfまでの数字が書かれた面があるとします。このとき、出た目の合計がターゲットの値と一致するような振り方(全 fd 通りのうち)の数を、10^9 + 7 で割った余りとして求めます。
例えば、d = 2、f = 6、target = 7 の場合、答えは6になります。6面のサイコロ2つを振って合計が7になる組み合わせは、「1+6」「2+5」「3+4」「4+3」「5+2」「6+1」の6通り存在するためです。
解法のアプローチ
この問題は動的計画法(DP)を使うことで効率的に解けます。手順は以下の通りです。
- m := 10^9 + 7(剰余を取るための定数)を定義する
- d × (t + 1) のサイズのdpテーブルを作成し、すべて0で初期化する
- i を 0 から d − 1 まで繰り返す
- j を 0 から t まで繰り返す
- i = 0 の場合:j が 1 以上 f 以下なら dp[i, j] := 1、それ以外は 0 とする
- それ以外の場合:
- l を 1 から f まで繰り返し、j − l > 0 であれば dp[i, j] := dp[i, j] + dp[i − 1, j − l] を計算し、さらに dp[i, j] := dp[i, j] mod m を適用する
- j を 0 から t まで繰り返す
- 最後に dp[d − 1][t] mod m を返す
アルゴリズムのポイント
このDPにおいて、dp[i][j] は「i番目までのサイコロを使って合計 j を作る方法の数」を表します。最初のサイコロ(i = 0)については、出目 j が 1 以上 f 以下であればちょうど1通りのみ存在します。2個目以降のサイコロでは、直前の状態に各出目 l(1〜f)を加えることで遷移できるため、dp[i−1][j−l] の総和を計算します。また、答えが巨大な数になるのを防ぐため、毎回 10^9 + 7 の剰余を取っている点も重要なポイントです。
Pythonでの実装例
理解を深めるために、以下の実装例を見てみましょう。
class Solution(object):
def numRollsToTarget(self, d, f, t):
mod = 1000000000+7
dp = [[0 for i in range(t+1)] for j in range(d)]
for i in range(d):
for j in range(t+1):
if i == 0:
dp[i][j] = 1 if j>=1 and j<=f else 0
else:
for l in range(1,f+1):
if j-l>0:
dp[i][j]+=dp[i-1][j-l]
dp[i][j]%=mod
return dp[d-1][t] % mod
ob = Solution()
print(ob.numRollsToTarget(2,6,7))
入力
2 6 7
出力
6
-
Pythonで二分木の合計がkとなるパスの数を数える方法
問題の概要 二分木と値 k が与えられたとき、あるノードからその子孫へ向かうパスのうち、通過するノードの値の合計がちょうど k と一致するものがいくつ存在するかを求める問題です。 例えば、次のような二分木を考えてみましょう。 このとき k = 5 であれば、出力は 2 となります。条件を満たすパスは [2, 3] と [1, 4] の2つだからです。 解き方のアプローチ:累積和(prefix sum)の活用 この問題は「累積和(prefix sum)」というテクニックを使うことで、全ノードを一度だけ訪問する効率的なアルゴリズムとして解けます。考え方の手順は以下の通りです。 count:マッ
-
Pythonプログラムで数の偶数の約数の合計を求める方法
この記事では、以下の問題文に対する解決策について詳しく解説します。 問題文:ある数が与えられたとき、その数のすべての偶数の約数(因子)の合計を求めて表示します。 アプローチ まず、与えられた数が奇数であるかどうかを確認します。奇数には偶数の約数が存在しないため、その場合は 0 を返します。 数が偶数である場合は、実際の計算に進みます。ここでのポイントは、20(つまり1)以外のすべての項を掛け合わせることで、偶数の約数の合計が得られるという点です。 偶数の約数からすべての奇数を取り除くために、20 に相当する「1」を無視します。この処理を行うことで、残るのは偶数の約数のみとなります。なお、2 は