動態規划4--最佳加法表達式
一、心得
心得:
動態規划因為有遞推表達式,所以一定可以寫成遞推和遞歸兩種寫法。
因為遞推一定可以寫成遞歸。
區別兩種問題:
在10個數字中放任意個加號使得組成的表達式的和最小。
狀態轉移方程:
將m個加號插入到n個數字組成的數字串中
V(m,n) 表示將m個加號插入到n個數字組成的數字串中組成的表達式和最小的表達式
i表示在第i個數后面插入加號
V(m,n) = Min{ V(m-1,i) + Num(i+1,n) } ( i = m … n-1)
表達式的最后一個加號添加在第 i 個數字后面
這個i要試遍所有的情況
在10個數字中放5個加號使得組成的表達式的和最小。
這就分在哪個位置上面放加號或者不放加號,有點像背包問題
將m個加號插入到n個數字組成的數字串中
V(m,n) = Min{ V(m-1,n-1) , V(m,n-1) }
這里可以把dp中的兩維變成1維么,不行,因為看這兩維表示的意義
在n個數字中插入m個加號,每一個加號在n個數字中的位置任意
一種是在把m個加號插入n個數字中
循環先m再n
一種是在n個數字中插入m個加號
循環先n再m
都要保證n比m大,n可以取m+1
m寫在n前面,n等於m+1,可以省掉一半的情況。寫法方便。
i表示插入的位置,位置要在m和n中間
3個加號插入5個數字中間,i的取值3到5????
這個好想:我3個加號插入5個數字中間,最后一個加號的取值肯定要保證之前兩個加號最少所需的三個數字
確定遞歸方程中每一個變量的取值范圍實在是重中之重
二、題目及分析
題意:
有一個由1..9組成的數字串.問如果將m個加號插入到這個數字串中,在各種可能形成的表達式中,值最小的那個表達式的值是多少。
輸入:
5 3
1 2 3 4 5
輸出:
24
假定數字串長度是n,添完加號后,表達式的最后一個加號添加在第 i 個數字后面,
那么整個表達式的最小值,就等於在前 i 個數字中插入 m – 1個加號所能形成的最小值,
加上第 i + 1到第 n 個數字所組成的數的值(i從1開始算)。
設V(m,n)表示在n個數字中插入m個加號所能形成
的表達式最小值,那么:
if m = 0
V(m,n) = n個數字構成的整數
else if n < m + 1
V(m,n) = ∞
else
V(m,n) = Min{ V(m-1,i) + Num(i+1,n) } ( i = m … n-1)
Num(i,j)表示從第i個數字到第j個數字所組成的數。數字編號從1開始算。此操作復雜度是O(j-i+1)
總時間復雜度:O(mn2) .(dp二維表已經加號的位置)
三、代碼及結果
遞推
1 /* 2 最佳加法表達式: 3 題意: 4 有一個由1..9組成的數字串.問如果將m個加號插入到這個數字串中,在各種可能形成的表達式中,值最小的那個表達式的值是多少。 5 輸入: 6 5 3 7 1 2 3 4 5 8 輸出: 9 24 10 11 */ 12 /* 13 預處理和排序都能很好的減少耗時 14 */ 15 #include<iostream> 16 #include<cstdio> 17 #include<cstring> 18 #include<algorithm> 19 using namespace std; 20 const int INF=0x3f3f3f3f; 21 const int N=1005; 22 int a[N],num[N][N],dp[N][N]; 23 //a[N]里面是存數字串 24 //num[i][j]表示數字串a[N]的第i位到第j位之間的數字串表示的數組 25 //dp[i][j]在i個數字中插入j個加號所能形成的表達式最小值 26 int main(){ 27 int n,m; 28 while(scanf("%d %d",&n,&m)){ 29 for(int i=1;i<=n;i++){ 30 scanf("%d",&a[i]); 31 } 32 //預處理,計算i到j數字串組成的數字 33 for(int i=1;i<=n;i++){ 34 num[i][i]=a[i];//只有一個數字 35 for(int j=i+1;j<=n;j++){ 36 num[i][j]=num[i][j-1]*10+a[j]; 37 } 38 } 39 memset(dp,0x3f,sizeof(dp)); 40 for(int i=1;i<=n;i++){ 41 dp[0][i]=num[1][i];//無加號時 42 } 43 //其實就是感覺在那個位置放不放加號 44 //這里n可以寫在m前面。要加一個限制條件n>m,好麻煩,所以m在前且n=m+1 45 //這里k的取值范圍就是m到n,k表示在第k個數后面插入加號 46 for(int i=1;i<=m;i++) 47 for(int j=i;j<=n;j++) 48 for(int k=i;k<=j;k++) 49 dp[i][j]=min(dp[i][j],dp[i-1][k]+num[k+1][j]); 50 cout<<dp[m][n]<<endl; 51 } 52 } 53
遞歸
1 /* 2 題意: 3 有一個由1..9組成的數字串.問如果將m個加號插入到這個數字串中,在各種可能形成的表達式中,值最小的那個表達式的值是多少。 4 輸入: 5 5 3 6 1 2 3 4 5 7 輸出: 8 24 9 子問題:將最后面的那個加號放在第i個數字的后面,計算前i個 10 數字的最佳加法表達式 11 狀態:V(m,n)表示在n個數字中插入m個加號所能形成 12 的表達式最小值 13 */ 14 //不懂的位置畫個實例,會很簡單,因為熟悉,所以容易 15 /* 16 心得: 17 動態規划因為有遞推表達式,所以一定可以寫成遞推和遞歸兩種寫法。 18 因為遞推一定可以寫成遞歸。 19 20 區別兩種問題: 21 22 在10個數字中放任意個加號使得組成的表達式的和最小。 23 狀態轉移方程: 24 將m個加號插入到n個數字組成的數字串中 25 V(m,n) 表示將m個加號插入到n個數字組成的數字串中組成的表達式和最小的表達式 26 i表示在第i個數后面插入加號 27 V(m,n) = Min{ V(m-1,i) + Num(i+1,n) } ( i = m … n-1) 28 表達式的最后一個加號添加在第 i 個數字后面 29 這個i要試遍所有的情況 30 31 32 33 在10個數字中放5個加號使得組成的表達式的和最小。 34 這就分在哪個位置上面放加號或者不放加號,有點像背包問題 35 將m個加號插入到n個數字組成的數字串中 36 V(m,n) = Min{ V(m-1,n-1) , V(m,n-1) } 37 38 這里可以把dp中的兩維變成1維么,不行,因為看這兩維表示的意義 39 在n個數字中插入m個加號,每一個加號在n個數字中的位置任意 40 41 一種是在把m個加號插入n個數字中 42 循環先m再n 43 一種是在n個數字中插入m個加號 44 循環先n再m 45 都要保證n比m大,n可以取m+1 46 m寫在n前面,n等於m+1,可以省掉一半的情況。寫法方便。 47 i表示插入的位置,位置要在m和n中間 48 3個加號插入5個數字中間,i的取值3到5???? 49 這個好想:我3個加號插入5個數字中間,最后一個加號的取值肯定要保證之前兩個加號最少所需的三個數字 50 51 確定遞歸方程中每一個變量的取值實在是重中之重 52 53 */ 54 #include<iostream> 55 #include<cstdio> 56 #include<algorithm> 57 using namespace std; 58 const int INF = 99999999; 59 int a[1005],num[1005][1005]; 60 int V(int m,int n) 61 { 62 if(m == 0)//無加號 63 return num[1][n]; 64 else if(n < m+1)//加號過多 65 return INF; 66 else 67 { 68 int t = INF; 69 //這里有點像回溯,回溯的話遞歸里面有循環 70 //說說實話,遞歸真的很簡單,就是把遞推公式寫進來 71 for(int i = m;i <= n-1;i++) //這里是n減一,排除了最后一個數字后面放加號的情況 72 t = min(t, V(m-1,i)+num[i+1][n]); 73 //不懂的位置畫個實例,會很簡單,因為熟悉,所以容易 74 return t; 75 } 76 } 77 int main() 78 { 79 int n,m; 80 while(~scanf("%d%d",&n,&m)) 81 { 82 for(int i = 1;i <= n;i++) 83 scanf("%d",&a[i]); 84 //預處理,計算i到j數字串組成的數字 85 for(int i = 1;i <= n;i++) 86 { 87 num[i][i] = a[i];//只有一個數字 88 for(int j = i+1;j <= n;j++) 89 { 90 //這個表達式也可以好好看一下 91 num[i][j] = num[i][j-1]*10 + a[j]; 92 } 93 } 94 cout<< V(m,n) <<endl; 95 } 96 return 0; 97 }