题目链接

石子合并

题目描述

堆石子排成一行,第 堆石子的质量为 。每次操作只能选择相邻的两堆石子合并,代价等于这两堆石子的质量之和。合并后形成的新石堆质量也为两者之和,并继续与左右相邻石堆相邻。目标是找到一种合并顺序,使得将所有石子合并成一堆的总代价最小。

解题思路

这是一个典型的区间动态规划问题。我们的目标是求将所有 堆石子合并为一堆的最小代价。

1. 状态定义

我们定义 为将区间 (即从第 堆到第 堆)的石子合并成一堆的最小代价。我们的最终目标是求解

2. 状态转移

对于区间 ,其最后一次合并必然是将两个已经合并好的相邻子区间合并而成的。我们可以枚举这个分割点 ,其中 。这意味着区间 是由区间 和区间 合并而成的。

  • 将区间 合并成一堆的最小代价是
  • 将区间 合并成一堆的最小代价是
  • 将这两堆合并的代价是它们各自的总质量之和,也就是从第 堆到第 堆所有石子的总质量。

为了快速计算任意区间的质量和,我们可以预处理一个前缀和数组 ,其中 。那么区间 的质量和就是

综合以上分析,状态转移方程为:

3. 初始化

  • 时,区间只包含一堆石子,无需合并,代价为 0。所以
  • 对于其他 (其中 ),可以初始化为一个极大值。

4. 遍历顺序

为了保证计算 时所依赖的子问题 都已经被计算过,我们需要按照区间的长度从小到大进行遍历。我们枚举区间长度 从 2 到 ,然后枚举区间的左端点 ,右端点 自然就是

最终的答案即为

代码

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>

using namespace std;

const int INF = 1e9;

int main() {
    int n;
    cin >> n;

    vector<int> w(n + 1);
    vector<int> s(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        cin >> w[i];
        s[i] = s[i - 1] + w[i];
    }

    vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));

    for (int len = 2; len <= n; ++len) {
        for (int i = 1; i <= n - len + 1; ++i) {
            int j = i + len - 1;
            dp[i][j] = INF;
            int cost = s[j] - s[i - 1];
            for (int k = i; k < j; ++k) {
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + cost);
            }
        }
    }

    cout << dp[1][n] << endl;

    return 0;
}
import java.util.Scanner;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();

        int[] w = new int[n + 1];
        int[] s = new int[n + 1];
        s[0] = 0;
        for (int i = 1; i <= n; i++) {
            w[i] = sc.nextInt();
            s[i] = s[i - 1] + w[i];
        }

        int[][] dp = new int[n + 1][n + 1];
        for (int i = 1; i <= n; i++) {
            Arrays.fill(dp[i], 0);
        }

        for (int len = 2; len <= n; len++) {
            for (int i = 1; i <= n - len + 1; i++) {
                int j = i + len - 1;
                dp[i][j] = Integer.MAX_VALUE;
                int cost = s[j] - s[i - 1];
                for (int k = i; k < j; k++) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k + 1][j] + cost);
                }
            }
        }

        System.out.println(dp[1][n]);
    }
}
n = int(input())
w = list(map(int, input().split()))

s = [0] * (n + 1)
for i in range(n):
    s[i + 1] = s[i] + w[i]

dp = [[0] * n for _ in range(n)]

for length in range(2, n + 1):
    for i in range(n - length + 1):
        j = i + length - 1
        dp[i][j] = float('inf')
        cost = s[j + 1] - s[i]
        for k in range(i, j):
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + cost)

print(dp[0][n - 1])

算法及复杂度

  • 算法:区间动态规划 (Interval Dynamic Programming)
  • 时间复杂度。我们有三层循环:第一层枚举区间长度 (从 2 到 ),第二层枚举区间起始点 (从 1 到 ),第三层枚举分割点 (从 )。
  • 空间复杂度。我们需要一个二维数组 来存储状态,以及一个一维数组 存储前缀和。