问题描述:

有若干小堆石子摆成环形,需要把它们合并成一个大堆,每次只能合并相邻的两小堆石子,每次合并的花费为两小堆石子之和,求合并成大堆的最小花费.

不仅可以摆成环形,还可以摆成线形(这是属于石子合并问题较简单的情况)

这个问题有最优子结构,因为只要最后的两堆石子的花费最少,那最终的大堆石子花费就最少(因为最后一次合成的花费是确定的,即全部石子数的总和),所以我们可以用动态规划来解决

建议在处理环形石子合并问题之前,先把线形石子合并问题解决清楚.

 环形问题可以转化为线形问题解决:如 0号,1号,...,n号石子环形排列等价于相同的两小堆线形石子排列 0号,1号,...,n号,0号(n+1号),1号(n+2号),...,n号(n+n号),

注意: 第二个周期的石子,既可以看作是0号石子也可以看作是n+1号石子

例如:

dp[1][3]为1号,2号,3号石子堆的最小花费;

dp[3][1]表示3号,4号,...n号,0号,1号的最小花费,又因为注意到:1号石子也可以看作是n+2号石子,所以dp[3][1]==dp[3][n+2]

即dp[i][j] == dp[i][j + size], size为石头堆总数,size == n+1, n为我们使用的标号

若J = j + size, 则我们可以用余数表示这种关系:dp[i][J % size] == dp[i][J]

----

设dp[i][j]为i号石子到j号石子合并的最优花费

可得递归式:

dp[i][j]=0; i == j

dp[i][j]=min(dp[i][k]+dp[k+1][j]+sum(i,k)+sum(k+1,j)); i <= k < j

php求解代码如下:

 1 <?php
 2 
 3 const INF = 65535;
 4 
 5 
 6 function stoneMerge(array $stoneArr)
 7 {
 8     $stoneCount = count($stoneArr);
 9 
10     for ($i = 0, $dp = []; $i < $stoneCount; $i++) {
11         for ($j = 0; $j < $stoneCount; $j++) {
12             $dp[$i][$j] = '000';
13         }
14     }
15 
16     for ($begin = 0; $begin < $stoneCount; $begin++) {
17         for ($step = 0, $i = 0, $j = $begin;
18              $step < $stoneCount;
19              $step++, $i++, $j++) {
20             if ($i == $j || $i == ($j % $stoneCount)) {
21                 $dp[$i][$j % $stoneCount] = '000';
22             } else {
23                 $min = INF;
24                 for ($k = $i; $k < $j; $k++) {
25                     $temp = $dp[$i][$k % $stoneCount] + $dp[($k + 1) % $stoneCount][$j % $stoneCount] + sumStone($stoneArr, $i, $j % $stoneCount);
26                     $min = $min > $temp ? $temp : $min;
27                 }
28                 $dp[$i][$j % $stoneCount] = str_pad($min, 3, '0', STR_PAD_LEFT);
29             }
30         }
31     }
32     foreach ($dp as $rows) {
33         foreach ($rows as $element) {
34             echo $element . PHP_EOL . PHP_EOL;
35         }
36         echo "<br/>";
37     }
38 }
39 
40 function sumStone(array &$stoneArr, $i, $j)
41 {
42     $size = count($stoneArr);
43 
44     $sum = 0;
45     if ($i == $j % $size) {
46         return $sum;
47     } else {
48         for ($begin = $i; $begin % $size != $j; $begin++) {
49             $sum += $stoneArr[$begin % $size];
50         }
51         $sum += $stoneArr[$begin % $size];
52         return $sum;
53     }
54 }
55 
56 $arr = [5, 8, 6, 9, 2, 3,];
57 stoneMerge($arr);

输入

5,8,6,9,2,3

输出:

000 013 032 056 071 084
081 000 014 037 050 061
055 081 000 015 028 039
034 059 081 000 011 019
015 033 053 081 000 005
008 024 044 070 085 000

 从结果可以看出,环形摆法和线形摆法的区别在于,线形摆法需要的只是矩阵从对角线开始的右上半部分,而环形摆法需要的是右上部分加左下部分,即整个矩阵。

我们可以看出,环形摆法的最小花费,就是0号~5号,1号~6号(0号),2号~7号(1号),……,5号~10号(4号)全部情况的最小值,81。

即min(dp[0][5], dp[1][6] = dp[1][0], dp[2][7] = dp[2][1], ... , dp[5][10] = dp[5][4]),对比线形摆法的最小值:dp[0][5]=84,可见环形摆法确实复杂不少。

另外,我们是把环形摆法转化为两个线形摆法来解决的,那环形摆法和真实的两个线形摆法的区别在哪里呢?

真实的两个线形摆法是一个更大的矩阵,但也只是取右上部分,而单个环形摆法只是这右上部分的一个子集

posted on 2019-04-16 22:30  SHQHDMR  阅读(1171)  评论(0编辑  收藏  举报