Pythonでストーンゲームの最大スコアを求めるプログラム(区間DP・メモ化再帰)
問題の概要
いくつかの石が一列に並んでおり、各石には数値が割り当てられています。これらの数値は配列 stoneValue として与えられます。各ラウンドでは、まず Amal が石の列を2つの部分に分割し、続いて Bimal がそれぞれの部分の値(その部分に含まれる全石の合計値)を計算します。そして Bimal は値が大きい方の部分を捨て、Amal のスコアは残った部分の値だけ増加します。両部分の値が等しい場合は、どちらを捨てるかを Amal が自由に決められます。次のラウンドは残った部分から始まり、石が1つだけ残った時点でゲーム終了です。Amal が獲得できるスコアの最大値を求めてください。
入力例とシミュレーション
入力が stoneValue = [7,3,4,5,6,6] の場合、出力は 24 になります。
1ラウンド目: Amal は列を [7,3,4] と [5,6,6] に分割します。左の合計は14、右の合計は17なので、Bimal は値の大きい右側を捨て、Amal のスコアは 14 になります。
2ラウンド目: 残った [7,3,4] を [7] と [3,4] に分割します。両方の合計は7で等しいので、Amal は左側を捨てることを選び、スコアは 14 + 7 = 21 になります。
3ラウンド目: 残った [3,4] を [3] と [4] に分割します。Bimal は値の大きい右側を捨て、Amal のスコアは 21 + 3 = 24 になります。
解法のアプローチ
この問題は「区間の部分和の前計算」と「メモ化再帰(トップダウンDP)」を組み合わせることで効率的に解けます。手順は以下のとおりです。
関数 dfs(start, end) を定義します。区間 [start, end] で得られる最大スコアを返します。
start >= end の場合(石が1つ以下)は、これ以上分割できないため 0 を返します。
max_score := 0 で初期化します。
cut を start から end-1 まで動かしながら以下を繰り返します。
sum1 := 区間 [start, cut] の部分和
sum2 := 区間 [cut+1, end] の部分和
sum1 > sum2 のとき:score := sum2 + dfs(cut+1, end)
sum1 < sum2 のとき:score := sum1 + dfs(start, cut)
等しいとき:score := sum1 + max(dfs(start, cut), dfs(cut+1, end))
max_score := max(score, max_score) ―― この更新は条件分岐の外で、すべての cut に対して必ず行うのが重要なポイントです。特定の分岐内だけで更新すると誤った結果になります。
max_score を返します。
関数 getPartialSum() を定義し、任意の区間 [i, j] の石の合計を partial_sum[i][j] として前計算しておきます。
メイン処理では、n := stoneValue のサイズ、partial_sum := n × n のゼロ初期化二次元配列を用意し、getPartialSum() を呼び出した後、dfs(0, n-1) を返します。
実装例
以下にPythonでの実装例を示します。再帰による重複計算を避けるため、functools.lru_cache によるメモ化を追加しています。
from functools import lru_cache
def solve(stoneValue):
n = len(stoneValue)
# 区間 [i, j] の石の合計を前計算
partial_sum = [[0] * n for _ in range(n)]
for i in range(n):
partial_sum[i][i] = stoneValue[i]
for j in range(i + 1, n):
partial_sum[i][j] = partial_sum[i][j - 1] + stoneValue[j]
@lru_cache(maxsize=None)
def dfs(start, end):
if start >= end:
return 0
max_score = 0
for cut in range(start, end):
sum1 = partial_sum[start][cut]
sum2 = partial_sum[cut + 1][end]
if sum1 > sum2:
score = sum2 + dfs(cut + 1, end)
elif sum1 < sum2:
score = sum1 + dfs(start, cut)
else:
score = sum1 + max(dfs(start, cut), dfs(cut + 1, end))
max_score = max(score, max_score)
return max_score
return dfs(0, n - 1)
stoneValue = [7, 3, 4, 5, 6, 6]
print(solve(stoneValue))
入力
[7,3,4,5,6,6]
出力
24
計算量について
部分和の前計算に O(n²)、DFSの状態数は O(n²)、各状態で分割位置を最大 O(n) 通り試すため、全体の計算量は O(n³) となります。メモ化によって同じ区間の再計算が不要になるため、素朴な再帰(指数時間)に比べて大幅に高速化できます。
-
Pythonで制約付きの建物の最大高さを求めるプログラム
問題の概要整数 n と制約リスト restrictions が与えられたとします。私たちは都市に n 棟の新しい建物を一列に建てようとしていますが、高さに関するいくつかの制限があります。建物には左から順に 1 から n までの番号が付けられており、各制約は restrictions[i] = (id_i, max_height_i) の形式で表され、「id_i 番の建物の高さは max_height_i 以下でなければならない」ことを意味します。建物の高さに関する都市の規則は以下のとおりです。各建物の高さは 0 以上でなければなりません。1 番の建物(最初の建物)の高さは必ず 0 です。隣接す
-
Pythonで数値を削除して最大の加算スコアを求めるプログラム|区間DPによる解法
問題の概要 数値のリスト nums が与えられます。次のような操作を考えます。 リストの先頭と末尾以外から数値を1つ選び、取り除きます。 その際、「選んだ数値 + 両隣の数値」の合計がスコアに加算されます。 この操作は、先頭と末尾を選ばない限り何度でも繰り返せます。 このとき、最終的に得られるスコアの最大値を求めるのが目的です。 入力例と動作の確認 入力が nums = [2, 3, 4, 5, 6] の場合、出力は 39 になります。手順は以下の通りです。 5 を選択:スコアは (4 + 5 + 6) = 15、配列は [2, 3, 4, 6] になります。 4 を選択:スコアは (