動態規划4--最佳加法表達式


動態規划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 } 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM