算法訓練 最大的算式
時間限制:1.0s 內存限制:256.0MB
問題描述
題目很簡單,給出N個數字,不改變它們的相對位置,在中間加入K個乘號和N-K-1個加號,(括號隨便加)使最終結果盡量大。因為乘號和加號一共就是N-1個了,所以恰好每兩個相鄰數字之間都有一個符號。例如:
N=5,K=2,5個數字分別為1、2、3、4、5,可以加成:
1*2*(3+4+5)=24
1*(2+3)*(4+5)=45
(1*2+3)*(4+5)=45
……
N=5,K=2,5個數字分別為1、2、3、4、5,可以加成:
1*2*(3+4+5)=24
1*(2+3)*(4+5)=45
(1*2+3)*(4+5)=45
……
輸入格式
輸入文件共有二行,第一行為兩個有空格隔開的整數,表示N和K,其中(2<=N<=15, 0<=K<=N-1)。第二行為 N個用空格隔開的數字(每個數字在0到9之間)。
輸出格式
輸出文件僅一行包含一個整數,表示要求的最大的結果
樣例輸入
5 2
1 2 3 4 5
1 2 3 4 5
樣例輸出
120
樣例說明
(1+2+3)*4*5=120
題目解析:
本題涉及到一種算法——動態規划。
(1)動態規划思想
在分治求解過程中,有些子問題被重復計算了許多次。如果能夠保存已解決的子問題的答案,而在需要時再找出,就可以避免大量重復問題的計算,從而得到多項式時間算法。

(2)設計動態規划的步驟
① 找出最優解的性質,並刻畫其結構特征;
② 遞歸地定義最優值(寫出動態規划方程);
③ 以自底向上的方式計算出最優值(填入表格);
④ 根據計算最優值時得到的信息,構造一個最優解。
說明: a.步驟 ① ~ ③ 是動態規划算法的基本步驟;
b.在只需要求出最優值的情況,步驟 ④ 可以省略;若需要求出一個最優解,則必須要有第 ④ 步。
(3)動態規划的特征
① 最優子結構
當問題的最優解包含了其子問題的最優解,稱該問題具有最優子結構性質。
② 重疊子問題
在用遞歸算法自頂向下解問題時,每次產生的子問題並不總是新問題,有些子問題被反復計算多次。動態規划算法正是利用了這種子問題的重要性質,對每一個子問題只求 解一次,而后將其解保存在一個表格中,在以后盡可能多地利用這些子問題。
以題目給出的樣例輸入為例,分析動態規划算法:
(1)利用 sum 數組將前 i 個數之和保存。

(2)利用 dp 數組來保存前 i 個數有 0 個乘號時的最大值(全加時的值,與 sum 數組相同),即 dp[i][0];

(3)
在動態規划算法中,從第二( i 從 2 開始)個數后開始加乘號,前 i 個數循環累加 i - 1 (j 從 1 開始,到 i -1 結束,且不能大於 k)個乘號,乘號位置循環從第一個數后的位置到第 i 個數前的位置(p 從 2 開始, 到 i 結束);
step 1: i = 2 j = 1 p = 2 說明:前兩個數,有一個乘號,位置在第二個數前面
dp[2][1] = 0 說明:前兩個數一個乘號時,值為 0 (表1.2中 dp[2][1])
dp[1][0] x ( sum[2] - sum[1] ) = 2 說明:前一個數沒有乘號乘上前兩個數之和減去前一個數之和,即前一個數乘第二個數(1*2 = 2)
dp[2][1] = max( 0 , 2 ) 說明:填入 dp 表中

step 2: i = 3 j = 1 p = 2 說明:前三個數,有一個乘號,位置在第二個數前面
dp[3][1] = 0 說明:前三個數一個乘號時,值為 0 (表1.2中 dp[3][1])
dp[1][0] x ( sum[3] - sum[1] ) = 2 說明:前一個數沒有乘號乘上前三個數之和減去前一個數之和,即前一個數乘前兩個數之和(1*(2+3) = 5)
dp[3][1] = max( 0 , 5 ) 說明:填入 dp 表中

step 3: i = 3 j = 1 p = 3 說明:前三個數,有一個乘號,位置在第三個數前面
dp[3][1] = 5 說明:前三個數一個乘號時,值為 0 (表1.2中 dp[3][1])
dp[2][0] x ( sum[3] - sum[2] ) = 9 說明:前兩個數沒有乘號乘上前三個數之和減去前兩個數之和,即前一個數乘第三個數之和((1+2)* 3) = 9)
dp[3][1] = max( 5 , 9 ) 說明:填入 dp 表中

. . . . . .
只到所有的循環執行結束,一共 19 步。dp 表最終結果為:

當 5 個數有 2 個乘號時,最大值應為 dp[5][2] = 120。在循環執行過程中,我們不用擔心 dp[p-1][j-1] * (sum[i] - sum[p-1]) 究竟是那幾個數得到的結果,而使用它的值就可以啦,這就是動態規划最重要的特性之一!
示例代碼:
1 import java.io.BufferedReader; 2 import java.io.IOException; 3 import java.io.InputStreamReader; 4 5 public class Main { 6 public static void main(String[] args) throws IOException { 7 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 8 String[] str = br.readLine().split(" "); 9 int n = Integer.parseInt(str[0]); 10 int k = Integer.parseInt(str[1]); 11 12 long[][] dp = new long[n+1][k+1]; //dp[i][j]表示前i個數中有j個乘號時,所得最大值 13 int[] sum = new int[n+1]; //前i個數之和 14 15 str = br.readLine().split(" "); 16 for(int i = 1; i <= n; i++) { 17 sum[i] = sum[i-1] + Integer.parseInt(str[i-1]); 18 } 19 20 //沒有乘號的情況,即連加的情況 21 for(int i = 1; i <= n; i++) { 22 dp[i][0] = sum[i]; 23 } 24 //動態規划 25 for(int i = 2; i <= n; i++) { //前i個數 26 for(int j = 1; j <= i-1 && j <= k; j++) { //乘號的個數 27 for(int p = 2; p <= i; p++) { //乘號的位置 28 dp[i][j] = max(dp[i][j], dp[p-1][j-1] * (sum[i] - sum[p-1]));//求前i個數有j個乘號時的最大值 29 } 30 } 31 } 32 33 System.out.println(dp[n][k]); 34 } 35 36 /** 37 * 求最大數 38 * @param a 參數1 39 * @param b 參數2 40 * @return a b中的最大數 41 */ 42 private static long max(long a, long b) { 43 return a>b?a:b; 44 } 45 }