在動態規劃中,常常遇到形以下式的狀態轉移方程:ios
m(i,j)=min{m(i,k-1),m(k,j)}+w(i,j)(i≤k≤j)(min也能夠改成max)函數
上述的m(i,j)表示區間[i,j]上的某個最優值。w(i,j)表示在轉移時須要額外付出的代價。該方程的時間複雜度爲O(N3)優化
下面咱們經過四邊形不等式來優化上述方程,首先介紹什麼是「區間包含的單調性」和「四邊形不等式」spa
一、區間包含的單調性:若是對於 i≤i'<j≤j',有 w(i',j)≤w(i,j'),那麼說明w具備區間包含的單調性。(能夠形象理解爲若是小區間包含於大區間中,那麼小區間的w值不超過大區間的w值)code
二、四邊形不等式:若是對於 i≤i'<j≤j',有 w(i,j)+w(i',j')≤w(i',j)+w(i,j'),咱們稱函數w知足四邊形不等式。(能夠形象理解爲兩個交錯區間的w的和不超太小區間與大區間的w的和)blog
下面給出兩個定理:get
一、若是上述的 w 函數同時知足區間包含單調性和四邊形不等式性質,那麼函數 m 也知足四邊形不等式性質博客
咱們再定義 s(i,j) 表示 m(i,j) 取得最優值時對應的下標(即 i≤k≤j 時,k 處的 w 值最大,則 s(i,j)=k)。此時有以下定理io
二、假如 m(i,j) 知足四邊形不等式,那麼 s(i,j) 單調,即 s(i,j)≤s(i,j+1)≤s(i+1,j+1)。class
好了,有了上述的兩個定理後,咱們發現若是w函數知足區間包含單調性和四邊形不等式性質,那麼有 s(i,j-1)≤s(i,j)≤s(i+1,j) 。
即原來的狀態轉移方程能夠改寫爲下式:
m(i,j)=min{m(i,k-1),m(k,j)}+w(i,j)(s(i,j-1)≤k≤s(i+1,j))(min也能夠改成max)
因爲這個狀態轉移方程枚舉的是區間長度 L=j-i,而 s(i,j-1) 和 s(i+1,j) 的長度爲 L-1,是以前已經計算過的,能夠直接調用。
不只如此,區間的長度最多有n個,對於固定的長度 L,不一樣的狀態也有 n 個,故時間複雜度爲 O(N^2),而原來的時間複雜度爲 O(N^3),實現了優化!
從此只須要根據方程的形式以及 w 函數是否知足兩條性質便可考慮使用四邊形不等式來優化了。
以上描述狀態用 m(i,j),後文用的 dp[i][j],所表明含意是相同的,特此說明。
以石子合併問題爲例。
例若有6堆石子,每堆石子數依次爲3 4 6 5 4 2
由於是相鄰石子合併,因此不能用貪心(每次取最小的兩堆合併),只能用動歸。(注意:環形石子的話,必需要考慮最後一堆和第一堆的合併。)
例如:一個合併石子的方案:
第一次合併 3 4 6 5 4 2 ->7
第二次合併 7 6 5 4 2 ->13
第三次合併 13 5 4 2 ->6
第四次合併 13 5 6 ->11
第五次合併 13 11 ->24
總得分=7+6+11+13+24=61 顯然,比貪心法得出的合併方案(得分:62)更優。
動歸分析相似矩陣連乘等問題,得出遞推方程:
設 dp[i][j] 表示第 i 到第 j 堆石子合併的最優值,sum[i][j] 表示第 i 到第 j 堆石子的總數量。
(能夠在計算開始先作一遍求全部的 sum[i],表示求出全部第1堆到第i堆的總數量。則 sum[i][j]=sum[j]-sum[i]。這樣計算比較快。)
那麼就有狀態轉移公式:
這裏 i<=k<j
普通解法須要 O(n^3)。下面使用四邊形不等式進行優化。
首先判斷是否符合區間單調性和四邊形不等式。
i i' j j'
3 4 6 5 4 2
單調性:
w[i',j] = 4+6+5=15 w[i,j'] =3+4+6+5+4+2=24
故w[i',j] <= w[i,j'] 知足單調性
四邊形不等式:
w[i,j] + w[i',j'] = (3+4+6+5) + (4+6+5+4+2) = 18+21 = 39
w[i',j] + w[i,j'] = (4+6+5) + (3+4+6+5+4+2) = 15 + 24 = 39
故 w[i,j] + w[i',j'] <= w[i',j] + w[i,j']
故石子合併可利用四邊形不等式進行優化。
利用四邊形不等式,將原遞推方程的狀態轉移數量進行壓縮(即縮小了k的取值範圍)。
令 s[i][j]=min{k | dp[i][j] = dp[i][k-1] + dp[k][j] + w[i][j]},即計算出 dp[i][j] 時的最優的 k 值(本例中尋優爲取最小)
也能夠稱爲最優決策時的 k 值。因爲決策 s 具備單調性,所以狀態轉移方程中的 k 的取值範圍可修改成 :
s[i,j-1] <= s[i,j] <= s[i+1,j]
邊界:s[i,i] = i
由於 s[i,j] 的值在 m[i,j] 取得最優值時,保存和更新,所以 s[i,j-1] 和 s[i+1,j] 都在計算 dp[i][j-1] 以及 dp[i+1][j] 的時候已經計算出來了。
所以,s[i][j] 即 k 的取值範圍很容易肯定。
根據改進後的狀態方程,以及 s[i][j] 的定義方程,能夠很快的計算出全部狀態的值。計算過程能夠以下表所示(相似於矩陣連乘的打表)。
狀態表(若是是環形石子合併,須要打2n*2n的表)
3 4 6 5 4 2
例如:
計算dp[1][3],因爲s[1][2]=1,s[2][3]=2,則k值的取值範圍是1<=k<=2
則,dp[1][3]=min{dp(1,1)+dp(2,3)+13, dp(1,2)+dp(3,3)+13}=min{10+13, 7+13}=20,將其填到狀態表。同時,因爲取最優值的k等於2,則將其填到s表。
同理,能夠計算其餘狀態表和s表中的值。
dp[2][4]=min{dp(2,2)+dp(3,4)+15, dp(2,3)+dp(4,4)+15}=min{11+15, 10+15}=25
k=3
從表中能夠看出,當計算dp[2][5]的時候,因爲s[ i,j-1]=s[ 2,4]=3,s[ i+1,j]=s[3,5]=3,此時k的取值範圍已經限定爲只有一個,大幅縮短了尋找最優解的時間。
這裏給出程序代碼:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 5 const int N=205; 6 const int INF=0x7fffffff; 7 int n; 8 int a[N],sum[N],dp[N][N],s[N][N]; 9 void f(); 10 int main() 11 { 12 while(~scanf("%d",&n)) 13 { 14 sum[0]=0; 15 for (int i=1;i<=n;i++) 16 { 17 scanf("%d",&a[i]); 18 sum[i]=sum[i-1]+a[i]; 19 } 20 f(); 21 printf("%d\n",dp[1][n]); 22 } 23 return 0; 24 25 } 26 void f() 27 { 28 for (int i=1;i<=n;i++) dp[i][i]=0,s[i][i]=i; 29 for (int r=1;r<n;r++) 30 { 31 for (int i=1;i<n;i++) 32 { 33 int j=i+r; 34 if(j>n) break; 35 dp[i][j]=INF; 36 for (int k=s[i][j-1];k<=s[i+1][j];k++) 37 { 38 if(dp[i][j]>dp[i][k]+dp[k+1][j]) 39 { 40 dp[i][j]=dp[i][k]+dp[k+1][j]; 41 s[i][j]=k; 42 } 43 } 44 dp[i][j]+=sum[j]-sum[i-1]; 45 } 46 } 47 }
本文轉載自網易博客:
http://blog.163.com/dqx_wl/blog/static/2396821452015111133052112/