-
Notifications
You must be signed in to change notification settings - Fork 0
Minimum Cost To Merge Stones #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: ボトムアップ、再帰、相互再帰 | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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)] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. こちらのコメントをご参照ください。 min_cost あたりが良いと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. min_cost としました。dp はなるべく使わないように気をつけます。 |
||
|
|
||
| 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): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i と j の出現回数が多く、登場するたびに i と j が何を表すかを考える認知負荷が高そうだと感じました。 left と right あたりにしたほうが認知負荷が低くなりそうだと思いました。 |
||
| 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] | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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] | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
自分も自力でたどり着けませんでした。 k=2 の場合は、連鎖行列積問題に似ているように感じました。
https://en.wikipedia.org/wiki/Matrix_chain_multiplication
最後のマージの直前の状態を作るための最小をコストを先に求めたあと、マージのコストを追加する、という点がポイントのようですが、自力で発想できる気がしませんでした。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
連鎖行列積問題は初めて知りました。二つの行列をまとめる処理や動的計画法で解ける点は似ていそうです。
よく復習しようと思います。