1.1问题描述:
问题描述:在一个圆形操场的四周摆放着n堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
要求:对于任意给定的n堆石子,计算合并成一堆的最小得分和最大得分。
1.2分析:石子合并问题使用的是动态规划法:
动态规划通常用来求解最优化问题,即一个问题有多个解,每个解都有一个目标值,我们希望寻找具有最优值(最小或最大)的解。
1.3石子合并问题设计思路
动态规划算法寻求石子合并时的最大,最小得分,选择相邻的两堆石子堆进行合并,其最终花费的代价与石子堆的排列顺序有关。根据其重叠子问题建立状态转移方程,利用程序进行求解。此问题具有重叠子结构。
因此从已有的 i 到 j 的最小得分,与从 i 到 k 的最小得分、从 k+1 到 j 的最小得分、当前步骤从 i 到 j 的合并得分的和的最小值,最大也同此想法。
1.4如何化曲为直
因为问题为首尾相连 所以要考虑化曲为直假设有四个石堆 1,2,3,4那么我们就要将1,4相邻合堆考虑进来。所以我们将数组扩展为{1,2,3,4,1,2,3,4}。
如果N-1次合并的全局最优解包含了每一次合并的子问题的最优解,那么经这样的N-1次合并后的得分总和必然是最优的。因此我们需要通过动态规划算法来求出最优解。在此我们假设有n堆石子,一字排开,合并相邻两堆的石子,每合并两堆石子得到一个分数,最终合并后总分数最少的。
遇到的问题 如何将环形问题化曲为直。
解决方案 通过扩展数组
举例:拆环成链后得到 1 2 3 4
所以,我们设f(i,j)表示第i堆石子到第j堆石子合并成一堆石子的最大得分,sum(i,j)表示第i到第j的和:
f(i,j) = max{f(i,k)+f(k+1,j)} +sum(i,j), i<=k <j,k表示把i和j从中间分开,边界f[i][i] = 0
从环形的每一个点拆成多条链,分别求每条链的最大值,然后从这些最大值中找一个最大的,就是答案
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + (sum[j] - sum[i - 1]))
1.5问题求解过程
首先这是圆形的,石子堆圆形摆放,也就是最后一堆石子可以和第一堆石子合并,可以通过不同石子堆分别打头排列,解决这个问题。
先计算每一堆石子自己合并的情况,然后一个循环,2堆,3堆,4堆.......n堆石子合并;每i堆石子合并的时候,需要控制起始位置,i代表从哪一堆开始合并,j代表到哪一堆合并结束。
这样,i,j就控制了一个范围,在这个范围内分成r堆合并。但究竟r在哪个地方分开,才能获得最优值,我们是不知道的,需要i,j范围都遍历看一看。
还有就是递推公式,类比矩阵连乘问题理解,i到k的计算量+k到j的计算量+i到j的石子总数,就是当前m[i][j]的值,代表i堆到j堆合并的最大(最小)得分。
左右还有合并左右的分数这里计算合并左右的代价可以利用前缀和的方法 sum[j]-sum[i-1]。
for (int l = 1; l <= n; l ++) {
for (int i = 1; i + l - 1 <= n; i++) {
int j = i + l - 1;
if (l == 1) {
MAX[i][j] = 0;
MIN[i][j] = 0;
}
else {
for (int k = i; k < j; k++) {
int maxnum = MAX[i][k] + MAX[k + 1][j] + sum[j] - sum[i - 1];
int minnum = MIN[i][k] + MIN[k + 1][j] + sum[j] - sum[i - 1];
if (MAX[i][j] < maxnum) {
MAX[i][j] = maxnum;
}
if (MIN[i][j] == -1 ||MIN[i][j] > minnum) {
MIN[i][j] = minnum;
}
}
}
}
}