【题目描述】
在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
计算出将N堆石子合并成一堆的最小得分。
【输入】
第一行为一个正整数N (2≤N≤100);
以下N行,每行一个正整数,小于10000,分别表示第i堆石子的个数(1≤i≤N)。
【输出】
一个正整数,即最小得分。
【输入样例】
7
13
7
8
16
21
4
18
【输出样例】
239
【解题思路】
可以定义动态规划数组dp[i][j]
来表示将第i
堆到第j
堆石子合并成一堆的最小得分。为了计算这个值,需要知道在这个区间内任意位置k
将石子分成两部分后,各自合并成一堆的最小得分,再加上这两堆合并成一堆的得分。
-
初始化: 首先,需要一个数组来记录从第
i
堆到第j
堆石子的总数,这可以通过一个前缀和数组来实现,以便快速计算任意区间的石子总数。 -
动态规划求解:
- 按照区间的长度
len
从小到大枚举,因为任何大区间的最优解都是基于更小区间的最优解计算得来。 - 对于每个长度为
len
的区间,枚举区间的起点i
,则区间的终点j = i + len - 1
。 - 在每个区间
[i, j]
内,枚举可能的分割点k
,i ≤ k < j
,计算分割成两部分的最小得分,然后加上合并这两部分的代价(即这两部分石子数的总和),更新dp[i][j]
为所有可能分割方案中的最小值。
- 按照区间的长度
-
转移方程:
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[i][j])
,其中sum[i][j]
表示从第i
堆到第j
堆石子的总数,可以通过前缀和数组快速得到。
-
初始化:
dp[i][i] = 0
,因为单独一堆石子不需要合并,得分为0。- 前缀和数组
prefixSum[0] = 0
,便于计算区间和。
-
结果:
- 最终
dp[1][N]
就是将全部石子合并的最小得分.
- 最终
【代码实现】
#include <iostream>
#include <vector>
#include <limits.h> // INT_MAX
using namespace std;
int main() {
int N;
cin >> N;
vector<int> stones(N+1);
vector<int> prefixSum(N+1, 0);
for (int i = 1; i <= N; ++i) {
cin >> stones[i];
prefixSum[i] = prefixSum[i-1] + stones[i];
}
vector<vector<int>> dp(N+1, vector<int>(N+1, 0));
// 区间长度从2开始,直到N
for (int len = 2; len <= N; ++len) {
for (int i = 1; i <= N - len + 1; ++i) {
int j = i + len - 1;
dp[i][j] = INT_MAX;
for (int k = i; k < j; ++k) {
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + prefixSum[j] - prefixSum[i-1]);
}
}
}
cout << dp[1][N] << endl;
return 0;
}