石子合并
题目信息
路边玩法
有n堆石子放在路边一列,现在要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费(最小或最大)
操场玩法
一个圆形操场周围摆放着n堆石子圆圈,现在要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费(最小或最大)
问题分析
路边玩法
如果n-1次合并的全局最优解包含了每一次合并的子问题的最优解,那么经这样的n-1次合并后的花费总和必然是最优的。
操场玩法
如果把路边玩法看成石子合并问题,那么操场玩法就属于圆形石子合并问题。圆形石子合并经常转换为直线型来求。也就是说,把圆形结构看成是长度为原规模两倍的直线结构来处理,如果操场玩法的规模为n,所以相当于有一排石子 a1, a2, … an, a1, a2, …, an-1,该问题规模为2n-1。最后从规模是n的最优值找出最小值或最大值即可
算法设计
路边玩法
1. 确定合适的数据结构
采用一维数组 a[i]来记录第i堆石子的数量;
sum[i]来记录前i堆石子的总数量;
二维数组Min[i][j]、Max[i][j]来记录第i堆到第j堆石合并的最小和最大花费
2. 初始化
输入石子的堆数n,然后依次输入各堆石子的数量存储在a[i]中,
令Min[i][j]=0,Max[i][j]=0,sum[0]=0,计算sum[i].
3.循环阶段
按照递归式计算2堆石子合并{
ai, a i+1}的最小和最大花费
依次类推,直到求出所有堆的最小和最大花费
4.构造最优解
Min[1][n]和Max[1][n]是n堆石子合并的最小和最大花费。
如果还想知道具体的合并顺序,需要在求解的过程中记录最优决策,然后逆向构造最优解,
可以使用类似矩阵连乘的构造方法,用括号来表达合并的先后顺序。
操场玩法
从规模为n的最优值M[1][n],Min[2][n+1],Min[3][n+2],,,Min[n][2n-1]
中找最小值作为圆形石子合并的最小花费。
最大值同理
完美图解(以路边玩法为例)
剩下的可以看趣学算法
伪代码详解
路边玩法
void straight(int a[], int n) {
for (int i = 1; i <= n; i++) {
//初始化
Min[i][i] = 0, Max[i][i] = 0;
}
sum[0] = 0;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
for (int v = 2; v <= n; v++) {
//枚举合并的堆数规模
for (int i = 1; i <= n - v + 1; i++) {
//枚举起点
int j = i + v - 1; //枚举终点j
Min[i][j] = INF; //初始化最大值
Max[i][j] = -1; //初始化最小值
int tmp = sum[j] - sum[i - 1]; //记录i...j之间的石子数之和
for (int k = i; k < j; k++) {
Min[i][j] = min(Min[i][j], Min[i][k] + Min[k + 1][j] + tmp);
Max[i][j] = max(Max[i][j], Max[i][k] + Max[k + 1][j] + tmp);
}
}
}
}
操场玩法
void Circular(int a[], int n) {
for (int i = 1; i <= n-1; i++) {
a[n + i] = a[i