(1)問題描述
在一個圓形操場的四周擺放着 num 堆石子。先要將石子有次序地合並成一堆。規定每次只能選相鄰的 2 堆石子合並成新的一堆,並將新的一堆石子數記為該次合並的耗費力氣。試設計一個算法,計算將 n 堆石子合並成一堆的最省力氣數。
(2)算法思想
對於給定的 n 堆石子,當只有一堆時,不用搬,進而不耗費力氣,然后依次計算出從 2 堆 ~ num 堆石子的最優解,並且堆數遞增求最優解,依賴於上一步的解進行計算所得;
(3)算法思路
此解法和矩陣連乘類似,我們知道矩陣連乘也是每次合並相鄰的兩個矩陣,那么石子合並可以用矩陣連乘的方式來解決。
設 dp[i][j] 表示第 i 到第 j 堆石子合並的最優值,sum[i][j] 表示第 i 到第 j 堆石子的所耗費的力氣總數。動規方程如下:

(4)代碼展示
public class StoneMerge { /** * 記錄石子堆的數量 */ private static int num; /** * 記錄每堆石子的重量 */ private static int[] weight; /** * 記錄石子堆斷開的位置【便於計算局部最優解】 */ private static int[][] location; /** * 記錄石子堆局部最優解,以至於求得最終最優解【動規方程】 */ private static int[][] dp; /** * 初始化數據 */ private static void initData() { Scanner input = new Scanner(System.in); System.out.println("請輸入石子堆數量:"); num = input.nextInt(); weight = new int[num]; System.out.println("請輸入每堆石子的重量:"); for (int i = 0; i < weight.length; i++) { weight[i] = input.nextInt(); } // 定義成 int 類型的二維數組,創建完每個元素直接初始化為 0 dp = new int[num][num]; location = new int[num][num]; } /** * 計算最省最費力氣值 */ private static void dpFindMinStrength() { // 初始化 dp 數組 for (int m = 0; m < num; m++) { dp[m][m] = 0; // 一堆石子,不用搬,耗費力氣為 0 } for (int r = 2; r <= num; r++) { // 從 2 堆依次到 num 堆,分別計算最優值 for (int i = 0; i < num - r + 1; i++) { // 起始石子堆取值范圍 int j = i + r - 1; // 根據每次選取石子堆 r 和起始石子堆 i ,計算終止石子堆 int sum = 0; for (int x = i; x <= j; x++) { // 計算從石子堆 i 到 石子堆 j 合並時,最后兩堆使用的力氣總和 sum sum += weight[x]; } // 根據動規方程,從局部最優解中計算當前從石子堆 i 到石子堆 j 合並所使用的的力氣總和 dp[i][j] = dp[i + 1][j] + sum; // 計算從 i 石子堆分開時,使用的力氣總和 location[i][j] = i; // 標記從第 i 石子堆分開位置 for (int k = i + 1; k < j; k++) { // 需要統計從 k 【k ∈ (i, j)】石子堆分開,使用的力氣總和 int temp = dp[i][k] + dp[k + 1][j] + sum; // 計算從 k 石子堆分開時,使用的力氣總和 if (temp < dp[i][j]) { dp[i][j] = temp; location[i][j] = k; } } } } } /** * 輸出 */ private static void print() { System.out.println("動規數組【不同堆數合並石子所費力氣】:"); for (int i = 0; i < num; i++) { for (int j = 0; j < num; j++) { System.out.print(dp[i][j] + " "); } System.out.println(); } System.out.println("不同堆數合並石子最省力氣斷開位置最優解:"); for (int i = 0; i < num; i++) { for (int j = 0; j < num; j++) { System.out.print(location[i][j] + " "); } System.out.println(); } } public static void main(String[] args) { // 初始化數據 initData(); // 計算最省最費力氣值 dpFindMinStrength(); // 輸出 print(); } }
(5)輸入輸出
請輸入石子堆數量: 4 請輸入每堆石子的重量: 4 4 5 9 動規數組【不同堆數合並石子所費力氣】: 0 8 21 43 0 0 9 27 0 0 0 14 0 0 0 0 不同堆數合並石子最省力氣分開位置最優解【下標從數組 0 開始分開】: 0 0 1 2 0 0 1 2 0 0 0 2 0 0 0 0
(6)總結
石子合並問題完全提現了動態規划的核心思想,先求解子問題的解【子問題求解不相互獨立,相互依賴】,然后從這些子問題的解中得到原問題的解,進而得到該問題的最優解。
