經常使用算法思想之動態規劃的區間子集思想

思路:運用動態規劃去解決問題,這個時候子問題並非屬於父問題的"前綴",也不是屬於父問題的"後綴",而是屬於父問題的某個區間以內。數組

示例

矩陣線程

給一個矩陣序列 ABCD,它相乘的方式能夠表示爲 (ABC)D=AB(CD)=A(BCD)=...,不一樣的添加括號方式會致使不一樣的計算次數,好比bash

A: 10 × 30 matrix
B : 30 × 5 matrix
C : 5 × 60 matrix
複製代碼

那麼ui

(AB)C = (10×30×5) + (10×5×60) 
       = 1500 + 3000 
       = 4500 操做
 A(BC) = (30×5×60) + (10×30×60) 
       = 9000 + 18000 
       = 27000 操做
複製代碼

針對這種現象,如何添加括號才能使得操做次數最少呢?
在輸入中,矩陣用一個數組表示,好比輸入40 20 30 10 30表示矩陣A有40行,20列,矩陣B有個20行,30列,矩陣C有30行10列,矩陣D有10行30列
輸入規則爲spa

2   //表示總共有兩個輸入
5  //下一個要輸入的數組大小
1 2 3 4 5  //數組的值
3 3 3
複製代碼

分析以下:
假設有以下形式的矩陣作乘法 .net

若是直接按照順序來計算,先乘A.B,獲得的結果再乘C

若是優先運算 B.C ,結果再乘A

能夠看到第二種方式消耗的時間會更少。擴展到假設有n個矩陣相乘,不管是怎麼添加括號,改變執行順序,最後必定是其它的都計算完畢,只須要計算剩餘的兩個矩陣相乘。假設區分最後兩部分的下標是k,那麼最後一次執行爲

(A_0,...,A_{k-1}).(A_k,...,A_{n-1})

要達到最後一步,則須要把兩個部分的結果分別計算出來,假設先計算(A_i,...,A_{k-1}),類推上面的經驗,一定存在一個節點i來劃分獲得線程

(A_0,...,A_{i-1}).(A_i,...,A_{k-1})

能夠看到要獲得最終問題的解,這樣一層層倒推下來,須要解決相似 (A_i,...,A_{k-1})這樣的,屬於原始問題的某個區間內子集的問題。3d

以數據長度爲4舉例,即3個矩陣ABC相乘,但願獲得最少的計算次數。
最終要計算的結果用dp(0,3),其中0表示輸入的矩陣數組中的下標爲0的位置,3是下標爲3的位置,以此表示最終要囊括ABC三個矩陣。
按照上述分析,要計算dp(0,3),它的最後一步有一下兩種劃分方式code

  • dp(0,1)與dp(1,3),即計算規則爲A(BC)
  • dp(0,2)與dp(2,3),即計算規則爲(AB)C

比較兩者那種方式計算最少便可獲得最終結果
要獲得dp(1,3)則須要知曉dp(1,2)與dp(2,3)的須要最少的次數cdn

固然這裏只須要直接相乘便可blog

同理計算dp(0,2)

整個過程用圖表示以下:

目標爲計算圖中的問號dp(0,3),表格中的橫軸表示開始計算的下標,縱軸表示結束計算的下標,這種表示方式,當橫軸值大於縱軸值時(如座標2,0),能夠忽略,不須要計算。根據最後的劃分結果,要獲得dp(0,3)先的計算A方案:dp(0,1)與dp(1,3) 或者是 B方案:dp(0,2)與dp(2,3)

A方案的計算用 x 表示計算過,B方案的計算用 o 表示計算過

dp(0,1)和dp(2,3)分表表示一個矩陣,不涉及操做,也就是做爲初始值爲0
dp(0,2)和dp(1,3)能夠分別再劃分爲

  • dp(0,2):dp(0,1)與dp(1,2),其中dp(0,1)的結果能夠複用dp(0,1)
  • dp(1,3):dp(1,2)與dp(2,3),其中dp(2,3)的結果能夠複用dp(2,3)

特地只說明dp(0,1)和dp(2,3)的複用,是爲了代表結果的可複用性,不須要重複計算

再次回顧上述過程

  1. 目標要獲得dp(0,3)
  2. 須要先計算dp(0,1) dp(1,3) dp(0,2) dp(2,3)
  3. 爲獲得2的結果,須要先獲取dp(0,1) dp(1,2) dp(2,3)的結果
  4. 爲獲得3,從下標之間的關係能夠看出,他們就是初始值,即只要有初始化的過程便可

如今逆向來看(從4到1),計算的過程能夠抽象爲以下的一個過程

先按照藍線箭頭部分計算對應位置的值,將它存儲起來,而後計算綠線部分的值,它會複用藍線部分的結果,最終獲得目標dp(0,3)。

class GFG {
public static void main (String[] args) throws IOException{
//處理數據的輸入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int round=Integer.parseInt(br.readLine());
GFG fgf=new GFG();
for(int i=1;i<=round;i++){
   int dataNum=Integer.parseInt(br.readLine());
   if(dataNum<=2){
       //少於1個矩陣沒有必要計算
       System.out.println(0);
       //記得要讀掉這部分數據,否則順序就亂了
       br.readLine();
       continue;
   }
   int[] arr=new int[dataNum];
   int count=0;
   for(String dataStr:br.readLine().split(" ")){
       arr[count++]=Integer.parseInt(dataStr);
   }
   int[][] dp=new int[dataNum][dataNum];
   for(int L=2;L<dataNum;L++){
       //L表示,從start開始後面還有幾個字符,L用來標識從表格左下角到右上角的一個過程
       for(int start=0;start<dataNum;start++){
           //start 表示開始計算的地方,start表示從表格左上角到右下角的一個過程
           int end=start+L;
           if(end>=dataNum){
                //不能超過數組的長度
               end=dataNum-1;
           }
           if(end-start<=1){
                //賦予初始值
               dp[start][end]=0;
               continue;
           }
           int min=Integer.MAX_VALUE;
           //比較當前全部可能的取值,並獲取最小的值做爲子問題的最優解
           for(int k=start+1;k<end;k++){
               int tempMin=dp[start][k]+dp[k][end]+arr[start]*arr[k]*arr[end];
               if(tempMin<min){
                   min=tempMin;
               }
           }
           dp[start][end]=min;
       }
   }
   System.out.println(dp[0][dataNum-1]);
}
}
}
複製代碼

附錄

matrix-chain-multiplication-dp

相關文章
相關標籤/搜索