C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で解く「石を1つの山にまとめる最小コスト」問題

問題概要

一列に並んだ N 個の石の山があり、i 番目の山には stones[i] 個の石が入っています。1 回の操作では、連続する K 個の山を 1 つの山にまとめることができ、このときのコストはその K 個の山に含まれる石の総数に等しくなります。

この問題では、すべての山を 1 つの山にまとめるための最小コストを求めます。なお、そもそも 1 つの山にまとめることが不可能な場合は -1 を返します。

具体例で確認

例えば、入力が [3, 2, 4, 1]、K = 2 の場合、出力は 20 になります。手順は以下の通りです。

  • 初期状態:[3, 2, 4, 1]
  • [3, 2] をコスト 5 でマージ → [5, 4, 1]
  • [4, 1] をコスト 5 でマージ → [5, 5]
  • [5, 5] をコスト 10 でマージ → [10]

合計コストは 5 + 5 + 10 = 20 となり、これが最小値となります。

解法のアプローチ

この問題は区間 DP(動的計画法)累積和を組み合わせて解きます。手順は以下の通りです。

  1. n を配列 stones のサイズとします。
  2. (n - 1) % (k - 1) が 0 でない場合は -1 を返します。これは、毎回 K 個ずつ減らして最終的に 1 つにできるかどうかの判定条件です。
  3. サイズ n + 1 の累積和配列 prefix を用意します。
  4. i = 1 から n まで、prefix[i] = prefix[i - 1] + stones[i - 1] を計算します。
  5. サイズ n × n の 2 次元配列 dp を定義します。dp[i][j] は「i 番目から j 番目までの山をまとめる最小コスト」を表します。
  6. 区間の長さ length を k から n まで伸ばしながら、以下を繰り返します。
    • i = 0、j = length - 1 から始めて、j < n の間、i と j を 1 ずつ増やします。
    • dp[i][j] を INT_MAX で初期化します。
    • 分割位置 mid を i から j 未満まで k - 1 ずつ進めながら、dp[i][j] = min(dp[i][j], dp[i][mid] + dp[mid + 1][j]) で更新します。
    • 区間 (j - i)(k - 1) で割り切れる場合(つまりこの区間を 1 つの山にまとめられる場合)、dp[i][j] += prefix[j + 1] - prefix[i] として区間全体の石の数を加算します。
  7. 最後に dp[0][n - 1] を返します。

C++による実装例

以下の実装を見ると理解が深まるでしょう。

#include <bits/stdc++.h>
using namespace std;
class Solution {
    public:
    int mergeStones(vector<int>& stones, int k){
        int n = stones.size();
        if ((n - 1) % (k - 1) != 0)
            return -1;
        vector<int> prefix(n + 1);
        for (int i = 1; i <= n; i++) {
            prefix[i] = prefix[i - 1] + stones[i - 1];
        }
        vector<vector<int>> dp(n, vector<int>(n));
        for (int length = k; length <= n; length++) {
            for (int i = 0, j = length - 1; j < n; i++, j++) {
                dp[i][j] = INT_MAX;
                for (int mid = i; mid < j; mid += k - 1) {
                    dp[i][j] = min(dp[i][j], dp[i][mid] + dp[mid + 1][j]);
                }
                if ((j - i) % (k - 1) == 0) {
                    dp[i][j] += prefix[j + 1] - prefix[i];
                }
            }
        }
        return dp[0][n - 1];
    }
};
main(){
    Solution ob;
    vector<int> v = {3,2,4,1};
    cout << (ob.mergeStones(v, 2));
}

入力

{3,2,4,1}, 2

出力

20
  1. C++でボードを正方形に分割する最小コストの求め方

    概念長さ p、幅 q のボードが与えられたとき、このボードを p×q 個の正方形に分割する際のコストを最小にすることを目指します。ボードの各辺にはそれぞれ切断コストが設定されており、コストが最小になるような切断の順序を選択することが求められます。例下図のようなボードを正方形に分割する場合、最適な切断方法は以下の通りです。このケースにおける合計最小コストは 65 となり、以下の手順で計算されます。初期値 : Total_cost = 0 Total_cost = Total_cost + 辺のコスト × 現在のピース数 コスト5 水平切断 : Cost = 0 + 5*1 = 5 コスト5 垂直

  2. C++で解くナイトの最短移動回数問題:メモ化再帰による効率的な解法

    問題概要無限に広がるチェス盤を考えます。座標は -∞ ~ +∞ の範囲に及び、ナイトは初期状態でマス [0, 0] に配置されています。ナイトの移動は下図のように8通りあり、それぞれ「縦または横の方向に2マス、その後それと直交する方向に1マス」という動きになります。この問題では、ナイトを目標のマス [x, y] まで移動させるのに必要な最小手数を求めます。なお、必ず目的地に到達できる(解が存在する)ことが保証されています。具体例たとえば入力が x = 5、y = 5 の場合、出力は 4 になります。これは次のような経路で到達できるためです。[0,0] → [2,1] → [4,2] → [3,