设有N堆石子排成一排,其编号为1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这N堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有4堆石子分别为 1 3 5 2, 我们可以先合并1、2堆,代价为4,得到4 5 2, 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24;
如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数N表示石子的堆数N。
第二行N个数,表示每堆石子的质量(均不超过1000)。
输出格式
输出一个整数,表示最小代价。
数据范围
1≤ N ≤300
输入样例:
4
1 3 5 2
输出样例:
22
题目大意:
输入一个n,接下来输入 n 个数,ai 表示合并第 i 个石子的质量,你需要将这些石子合并成一堆,每次只能选择相邻的两堆进行合并,合并的代价是两堆石子质量之和,你需要找出一种方式使得合并完的代价最小,并输出这个代价。
解题思路:
经典区间dp问题,从两方面分析这个问题:
- 状态表示: 用两维去表示状态,区间问题一般都可以这样表示,f(i, j) 表示从第 i 堆石子合并到第 j 堆石子所需代价,而这道题的属性是最小值,所以用f(i, j) 表示从第 i 堆合并到第 j 堆的最小代价。
- 状态计算: 看看如何划分这个集合,f[i][j] 可以从什么状态转移过来呢,第一重循环可以枚举长度len,从1 -> n 第二重枚举一个起点,i到i + len - 1 ,考虑最后一次合并,他一定是将两堆石子合并成一堆,所以第三重循环可以枚举分割点k ,所以每次合并的代价一定是max(f[l][r], f[l][k] + f[k + 1][r] + (sum[r] - sum[l - 1])) sum为前缀和数组,因为这次合并的代价要加上l - r 的总和,维护一个前缀和可以O1 时间去得到sum[l, r], k 的范围是i -> j - 1 ,因为至少要有一堆,以k为分界点,最后输出f(1, n) 即可。
Code:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 310;
int n;
int s[N];
int f[N][N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++)
{
int x;
cin >> x;
s[i] = s[i - 1] + x;//维护一个前缀和
}
for (int len = 2; len <= n; len ++)//长度是1的代价一定是0,所以不必从1开始枚举
for (int i = 1; i + len - 1 <= n; i ++)
{
int l = i, r = i + len - 1;
f[l][r] = 0x3f3f3f3f;//起初这堆的代价是不知道的,初始化成一个非常大的值即可
for (int k = l; k < r; k ++)
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
}
cout << f[1][n] << endl;
return 0;
}