From 6a1cb386339738339a5b0eb300bda759a61826c3 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sun, 27 Sep 2026 09:35:56 +0900 Subject: [PATCH 1/2] step1,2 --- 1000.Minimum-Cost-to-Merge-Stones/memo.md | 49 +++++++++++++++++++ 1000.Minimum-Cost-to-Merge-Stones/step1.py | 32 ++++++++++++ 1000.Minimum-Cost-to-Merge-Stones/step2.py | 25 ++++++++++ .../step2_mutual_recursion.py | 45 +++++++++++++++++ .../step2_recursion.py | 27 ++++++++++ 5 files changed, 178 insertions(+) create mode 100644 1000.Minimum-Cost-to-Merge-Stones/memo.md create mode 100644 1000.Minimum-Cost-to-Merge-Stones/step1.py create mode 100644 1000.Minimum-Cost-to-Merge-Stones/step2.py create mode 100644 1000.Minimum-Cost-to-Merge-Stones/step2_mutual_recursion.py create mode 100644 1000.Minimum-Cost-to-Merge-Stones/step2_recursion.py diff --git a/1000.Minimum-Cost-to-Merge-Stones/memo.md b/1000.Minimum-Cost-to-Merge-Stones/memo.md new file mode 100644 index 0000000..58be34b --- /dev/null +++ b/1000.Minimum-Cost-to-Merge-Stones/memo.md @@ -0,0 +1,49 @@ +# 1000. Minimum Cost to Merge Stones + +## step1 +30m以上考えて分からなかったので Hint として以下を見て解いた。合計で1h以上かかった。 + +k=2までは自力で考えられたが、k > 2 の答えに自力で辿り着けなかった。 + +https://discord.com/channels/1084280443945353267/1200089668901937312/1233370731371298897 + +思考過程: + +k=2のときの全探索(カタラン数)の枝刈りを考える。 +部分最適性が成り立つので、考える区間を徐々に短くしていけば良い。 +漸化式は + +dp[i][j] = min_{i \leq mid < j} (dp[i][mid] + dp[mid+1][j]) + sum(i, j) + +k > 2 のときには次元をもう一つ増やして、区間をいくつのpileとみなすかを考える。漸化式は + +2 <= num_pile <= k: + +dp[i][j][num_pile] = min_{i \leq mid < j} (dp[i][mid][1] + dp[mid+1][j][num_pile - 1]) + +num_pile == 1: + +dp[i][j][1] = dp[i][j][k] + sum(i, j) + + +## step2 + +見直してもう少し改善できることに気が付く。合体できる境界は k - 1 刻みにしか現れないので k > 2 のときにも2次元のままで考えられる。 + +計算量は O(n^3 / k) になった。 + +再帰でも書く。 + +相互再帰でも書く。 + +かなり時間がかかった。 + +## step3 +要復習。 + +TODO: ボトムアップ、再帰、相互再帰 + + + + + diff --git a/1000.Minimum-Cost-to-Merge-Stones/step1.py b/1000.Minimum-Cost-to-Merge-Stones/step1.py new file mode 100644 index 0000000..f20818f --- /dev/null +++ b/1000.Minimum-Cost-to-Merge-Stones/step1.py @@ -0,0 +1,32 @@ +import itertools +import math + +class Solution: + def mergeStones(self, stones: list[int], k: int) -> int: + num_stones = len(stones) + if (num_stones - 1) % (k - 1) != 0: + return -1 + + prefix_sum = list(itertools.accumulate(stones, initial=0)) + + dp = [[[float("inf")] * (k + 1) for _ in range(num_stones)] for _ in range(num_stones)] + + for i in range(num_stones): + dp[i][i][1] = 0 + + for length in range(2, num_stones + 1): + for i in range(num_stones - length + 1): + j = i + length - 1 + for num_pile in range(2, min(k, length) + 1): + for mid in range(i, j): + dp[i][j][num_pile] = min(dp[i][j][num_pile], dp[i][mid][1] + dp[mid + 1][j][num_pile - 1]) + + if not math.isinf(dp[i][j][k]): + dp[i][j][1] = dp[i][j][k] + prefix_sum[j+1] - prefix_sum[i] + + return dp[0][-1][1] + + + + + diff --git a/1000.Minimum-Cost-to-Merge-Stones/step2.py b/1000.Minimum-Cost-to-Merge-Stones/step2.py new file mode 100644 index 0000000..d22934b --- /dev/null +++ b/1000.Minimum-Cost-to-Merge-Stones/step2.py @@ -0,0 +1,25 @@ +import itertools + +class Solution: + def mergeStones(self, stones: list[int], k: int) -> int: + num_stones = len(stones) + if (num_stones - 1) % (k - 1) != 0: + return -1 + + prefix_sum = list(itertools.accumulate(stones, initial=0)) + + dp = [[0] * num_stones for _ in range(num_stones)] + + for length in range(2, num_stones + 1): + for i in range(num_stones - length + 1): + j = i + length - 1 + dp[i][j] = float("inf") + + for mid in range(i, j, k - 1): + dp[i][j] = min(dp[i][j], dp[i][mid] + dp[mid + 1][j]) + + if (length - 1) % (k - 1) == 0: + dp[i][j] += prefix_sum[j + 1] - prefix_sum[i] + + return dp[0][-1] + diff --git a/1000.Minimum-Cost-to-Merge-Stones/step2_mutual_recursion.py b/1000.Minimum-Cost-to-Merge-Stones/step2_mutual_recursion.py new file mode 100644 index 0000000..f739e15 --- /dev/null +++ b/1000.Minimum-Cost-to-Merge-Stones/step2_mutual_recursion.py @@ -0,0 +1,45 @@ +from functools import cache +import itertools + +class Solution: + def mergeStones(self, stones: list[int], k: int) -> int: + num_stones = len(stones) + if (num_stones - 1) % (k - 1) != 0: + return -1 + + @cache + def min_reduce_cost(i, j): + if i == j: + return 0 + + minimum = float("inf") + for mid in range(i, j, k - 1): + right_len = j - (mid + 1) + 1 + if (right_len - 1) % (k - 1) == 0: + right_cost = min_merge_cost(mid + 1, j) + else: + right_cost = min_reduce_cost(mid + 1, j) + + minimum = min(minimum, min_merge_cost(i, mid) + right_cost) + + return minimum + + prefix_sum = list(itertools.accumulate(stones, initial=0)) + + @cache + def min_merge_cost(i, j): + length = j - i + 1 + + if length == 1: + return 0 + + if (length - 1) % (k - 1) != 0: + return float("inf") + + base_cost = min_reduce_cost(i, j) + if base_cost == float("inf"): + return float("inf") + + return base_cost + prefix_sum[j + 1] - prefix_sum[i] + + return min_merge_cost(0, num_stones - 1) diff --git a/1000.Minimum-Cost-to-Merge-Stones/step2_recursion.py b/1000.Minimum-Cost-to-Merge-Stones/step2_recursion.py new file mode 100644 index 0000000..25030ba --- /dev/null +++ b/1000.Minimum-Cost-to-Merge-Stones/step2_recursion.py @@ -0,0 +1,27 @@ +from functools import cache +import itertools + +class Solution: + def mergeStones(self, stones: list[int], k: int) -> int: + num_stones = len(stones) + if (num_stones - 1) % (k - 1) != 0: + return -1 + + prefix_sum = list(itertools.accumulate(stones, initial=0)) + + @cache + def min_cost(i, j): + if i == j: + return 0 + + minimum = float("inf") + for mid in range(i, j, k - 1): + minimum = min(minimum, min_cost(i, mid) + min_cost(mid + 1, j)) + + length = j - i + 1 + if (length - 1) % (k - 1) == 0: + minimum += prefix_sum[j + 1] - prefix_sum[i] + + return minimum + + return min_cost(0, num_stones - 1) From 74756142b7c67e285a33070ff5ba323e50624e5f Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sun, 27 Sep 2026 20:49:30 +0900 Subject: [PATCH 2/2] suggested changes --- .../step1_revised.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 1000.Minimum-Cost-to-Merge-Stones/step1_revised.py diff --git a/1000.Minimum-Cost-to-Merge-Stones/step1_revised.py b/1000.Minimum-Cost-to-Merge-Stones/step1_revised.py new file mode 100644 index 0000000..479acd0 --- /dev/null +++ b/1000.Minimum-Cost-to-Merge-Stones/step1_revised.py @@ -0,0 +1,30 @@ +import itertools +import math + +class Solution: + def mergeStones(self, stones: list[int], k: int) -> int: + num_stones = len(stones) + if (num_stones - 1) % (k - 1) != 0: + return -1 + + prefix_sum = list(itertools.accumulate(stones, initial=0)) + + min_cost = [[[float("inf")] * (k + 1) for _ in range(num_stones)] for _ in range(num_stones)] + + for left in range(num_stones): + min_cost[left][left][1] = 0 + + for length in range(2, num_stones + 1): + for left in range(num_stones - length + 1): + right = left + length - 1 + for num_pile in range(2, min(k, length) + 1): + for mid in range(left, right): + min_cost[left][right][num_pile] = min( + min_cost[left][right][num_pile], + min_cost[left][mid][1] + min_cost[mid + 1][right][num_pile - 1] + ) + + if not math.isinf(min_cost[left][right][k]): + min_cost[left][right][1] = min_cost[left][right][k] + prefix_sum[right + 1] - prefix_sum[left] + + return min_cost[0][-1][1]