写在所有的前面:
本文采用C++实现代码
题目说明
题目
题目出处
《算法设计与分析》课上随堂测验
题目描述Description
设有 n 堆石子排成一排,其编号为 1,2,3,…,n。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 n 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
找出使总代价最小/最大的方法,输出最小/最大代价。
输入Input
第一行:一个整数 n,代表石子堆数
第二行:n 个整数,代表每堆石子质量
输出Output
第一行:一个整数,代表最小价值
第二行:一个整数,代表最大价值
样例Sample
输入:
4
4 4 5 9
输出:
43
54
限制Hint
解答说明
方案1:动态规划
解题思路
类似于矩阵连乘:本人写的《矩阵连乘(动态规划)(C/C++)最详尽代码注释》
https://blog.csdn.net/just_do_it_sq/article/details/142218288?spm=1001.2014.3001.5501
寻找最佳分隔点(最后一个合并的位置)
从连续 2 堆合并开始,不断增大到所有石子合并
从 i 到 j 开始遍历设分隔点 k ,取最优解
用前缀和队列优化多个数值的合并值计算
一般情况
最后分隔点为 k 时,最后一次合并后,合并总值为:左边总代价 + 右边总代价 + 总质量
特殊情况
一堆石子无法合并,mx[i][i] = mn[i][i] = 0
代码实现
#include<iostream>
using namespace std;
const int N = 100;//常量 N
int n;//n堆石子
int a[N], s[N];//石子堆数组,前缀和数组(前 i 项和)
int mx[N][N], mn[N][N];//最大值,最小值,mx[i][j]从 i 到 j 之间的石子堆合并
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);//石子堆初始化
s[i] = s[i - 1] + a[i];//前缀和初始化
}
memset(mn, 0x3f, sizeof mn);//给最小值数组 mn 初始化赋值,赋值结果为 0x3f 即无穷大,赋值长度为 mn 的大小
for (int i = 1; i <= n; i++)mn[i][i] = 0;//只有一堆石子时,合并值为 0
//探查最佳分隔点
for (int r = 2; r <= n; r++)// r 堆石子合并
{
for (int i = 1; i <= n - r + 1; i++)//从 i 开始
{
int j = i + r - 1;//到 j 结束
for (int k = i; k < j; k++)// k 为分隔点,即 i 到 j 中最后一次合并点,左边合并值 + 右边合并值 + 合并后一整堆提供的合并值
{
mx[i][j] = max(mx[i][j], mx[i][k] + mx[k + 1][j] + s[j] - s[i - 1]);//记录最大值
mn[i][j] = min(mn[i][j], mn[i][k] + mn[k + 1][j] + s[j] - s[i - 1]);//记录最小值
}
}
}
//输出即可
printf("%d\n%d", mn[1][n], mx[1][n]);
return 0;
}
其他解释
最详尽代码注释!!!