1. 程式人生 > >動態規劃的優化技巧

動態規劃的優化技巧

               

動態規劃演算法的優化技巧

福州第三中學   毛子青

[關鍵詞] 動態規劃、 時間複雜度、優化、狀態

[摘要]

動態規劃是資訊學競賽中一種常用的程式設計方法,本文著重討論了運用動態規劃思想解題時時間效率的優化。全文分為四個部分,首先討論了動態規劃時間效率優化的可行性和必要性,接著給出了動態規劃時間複雜度的決定因素,然後分別闡述了對各個決定因素的優化方法,最後總結全文。

[正文]

一、引言

動態規劃是一種重要的程式設計方法,在資訊學競賽中具有廣泛的應用。

使用動態規劃方法解題,對於不少問題具有空間耗費大、時間效率高的特點,因此人們在研究動態規劃解題時更多的注意空間複雜度的優化,運用各種技巧將空間需求控制在軟硬體可以承受的範圍之內。但是,也有一部分問題在使用動態規劃思想解題時,時間效率並不能滿足要求,而且演算法仍然存在優化的餘地,這時,就需要考慮時間效率的優化。

本文討論的是在確定使用動態規劃思想解題的情況下,對原有的動態規劃解法的優化,以求降低演算法的時間複雜度,使其能夠適用於更大的規模。

二、動態規劃時間複雜度的分析

使用動態規劃方法解題,對於不少問題之所以具有較高的時間效率,關鍵在於它減少了“冗餘”。所謂“冗餘”,就是指不必要的計算或重複計算部分,演算法的冗餘程度是決定演算法效率的關鍵。動態規劃在將問題規模不斷縮小的同時,記錄已經求解過的子問題的解,充分利用求解結果,避免了反覆求解同一子問題的現象,從而減少了冗餘。

但是,動態規劃求解問題時,仍然存在冗餘。它主要包括:求解無用的子問題,對結果無意義的引用等等。

下面給出動態規劃時間複雜度的決定因素:

時間複雜度=狀態總數*每個狀態轉移的狀態數*每次狀態轉移的時間

[1]

下文就將分別討論對這三個因素的優化。這裡需要指出的是:這三者之間不是相互獨立的,而是相互聯絡,矛盾而統一的。有時,實現了某個因素的優化,另外兩個因素也隨之得到了優化;有時,實現某個因素的優化卻要以增大另一因素為代價。因此,這就要求我們在優化時,堅持“全域性觀”,實現三者的平衡。

三、動態規劃時間效率的優化

3.1 減少狀態總數

我們知道,動態規劃的求解過程實際上就是計算所有狀態值的過程,因此狀態的規模直接影響到演算法的時間效率。所以,減少狀態總數是動態規劃優化的重要部分,本節將討論減少狀態總數的一些方法。

1、改進狀態表示

狀態的規模與狀態表示的方法密切相關,通過改進狀態表示減小狀態總數是應用較為普遍的一種方法。

例一、   Raucous Rockers 演唱組(USACO`96)

[問題描述]

現有n首由Raucous Rockers 演唱組錄製的珍貴的歌曲,計劃從中選擇一些歌曲來發行m張唱片,每張唱片至多包含t分鐘的音樂,唱片中的歌曲不能重疊。按下面的標準進行選擇:

(1)       這組唱片中的歌曲必須按照它們創作的順序排序;

(2)       包含歌曲的總數儘可能多。

輸入n,m,t,和n首歌曲的長度,它們按照創作順序排序,沒有一首歌超出一張唱片的長度,而且不可能將所有歌曲的放在唱片中。輸出所能包含的最多的歌曲數目。

(1≤n, m, t≤20)

[個人想法] 用dp[i][j]描述前i首歌用j張唱片所能錄製的最多歌曲,sum[i][j]為i到j首歌用1張唱片做多能錄的歌曲數目。

那麼dp[m][n]=max{dp[k][n-1]+sum[k+1][m]}  1=<k<=m-1 O(N^2)

類似IOI2000 post office問題,用最後一個來考慮問題,這樣才能分解成子問題。

[演算法分析]

本題要求唱片中的歌曲必須按照它們創作順序排序,這就滿足了動態規劃的無後效性要求,啟發我們採用動態規劃進行解題。

分析可知,該問題具有最優子結構性質,即:設最優錄製方案中第i首歌錄製的位置是從第j張唱片的第k分鐘開始的,那麼前j-1張唱片和第j張唱片的前k-1分鐘是前1..i-1首歌的最優錄製方案,也就是說,問題的最優解包含了子問題的最優解。

設n首歌曲按照寫作順序排序後的長度為long[1..n],則動態規劃的狀態表示描述為:

g[i, j, k],0≤i≤n,0≤j≤m,0≤k<t,表示前i首歌曲,用j張唱片另加k分鐘來錄製,最多可以錄製的歌曲數目,則問題的最優解為g[n,m,0]。由於歌曲i有發行和不發行兩種情況,而且還要分另加的k分鐘是否能錄製歌曲i。這樣我們可以得到如下的狀態轉移方程和邊界條件:

當k≥long[i],i≥1時:

g[i, j, k]=max{g[i-1,j,k-long[i]],g[i-1,j,k]}

當k<long[i],i≥1時:

g[i, j, k]=max{g[i-1,j-1,t-long[i]],g[i-1,j,k]}

規劃的邊界條件為:

當0≤k<t時:g[0,0,k]=0;

我們來分析上述演算法的時間複雜度,上述演算法的狀態總數為O(n*m*t),每個狀態轉移的狀態數為O(1),每次狀態轉移的時間為O(1),所以總的時間複雜度為O(n*m*t)。由於n,m,t均不超過20,所以可以滿足要求。

[演算法優化]

當資料規模較大時,上述演算法就無法滿足要求,我們來考慮通過改進狀態表示提高演算法的時間效率。

本題的最優目標是用給定長度的若干張唱片錄製儘可能多的歌曲,這實際上等價於在錄製給定數量的歌曲時儘可能少地使用唱片。所謂“儘可能少地使用唱片”,就是指使用的完整的唱片數儘可能少,或是在使用的完整的唱片數相同的情況下,另加的分鐘數儘可能少。分析可知,在這樣的最優目標之下,該問題同樣具有最優子結構性質,即:設D在前i首歌中選取j首歌錄製的最少唱片使用方案,那麼若其中選取了第i首歌,則D-{i}是在前i-1首歌中選取j-1首歌錄製的最少唱片使用方案,否則D前i-1首歌中選取j首歌錄製的最少唱片使用方案,同樣,問題的最優解包含了子問題的最優解。

改進的狀態表示描述為:

g[i, j]=(a, b),0≤i≤n,0≤j≤i,0≤a≤m,0≤b≤t,表示在前i首歌曲中選取j首錄製所需的最少唱片為:a張唱片另加b分鐘。由於第i首歌分為發行和不發行兩種情況,這樣我們可以得到如下的狀態轉移方程和邊界條件:

g[i, j]=min{g[i-1,j],g[i-1,j-1]+long[i]}

其中(a, b)+long[i]=(a’, b’)的計算方法為:

當long[i]≤t-b時: a’=a;     b’=b+long[i];

當long[i]>t-b時: a’=a+1;   b’=long[i];

規劃的邊界條件:

g[i,0]=(0,0)  0≤i≤n

這樣題目所求的最大值是:ans=max{k| g[n, k]≤(m-1,t)}

改進後的演算法,狀態總數為O(n2),每個狀態轉移的狀態數為O(1),每次狀態轉移的時間為O(1),所以總的時間複雜度為O(n2)。值得注意的是,演算法的空間複雜度也由改進前的O(m*n*t)降至優化後的O(n2)。

(程式及優化前後的執行結果比較見附件)

通過對本題的優化,我們認識到:應用不同的狀態表示方法設計出的動態規劃演算法的效能也迥然不同。改進狀態表示可以減少狀態總數,進而降低演算法的時間複雜度。在降低演算法的時間複雜度的同時,也降低了演算法的空間複雜度。因此,減少狀態總數在動態規劃的優化中佔有重要的地位。

2、選擇適當的規劃方向

    動態規劃方法的實現中,規劃方向的選擇主要有兩種:順推和逆推。在有些情況下,選取不同的規劃方向,程式的時間效率也有所不同。一般地,若初始狀態確定,目標狀態不確定,則應考慮採用順推,反之,若目標狀態確定,而初始狀態不確定,就應該考慮採用逆推。那麼,若是初始狀態和目標狀態都已確定,一般情況下順推和逆推都可以選用,但是,能否考慮選用雙向規劃呢?

雙向搜尋的方法已為大家所熟知,它的主要思想是:在狀態空間十分龐大,而初始狀態和目標狀態又都已確定的情況下,由於擴充套件的狀態量是指數級增長的,於是為了減少狀態的規模,分別從初始狀態和目標狀態兩個方向進行擴充套件,並在兩者的交匯處得到問題的解。

上述優化思想能否也應用到動態規劃之中呢?來看下面這個例子。

例二、   Divide (Merc`2000)

[問題描述]

有價值分別為1..6的大理石各a[1..6]塊,現要將它們分成兩部分,使得兩部分價值和相等,問是否可以實現。其中大理石的總數不超過20000。(英文試題詳見附件)

[個人理解]實質上和挑戰程式設計競賽P62中的多重部分和問題一樣(有n種大小不同的數字,每一種mi個,問是否可以從這些數字中選出若干個使得和切好為K)

如果用最樸素的理解,定義dp[i][j]為前i個數能否構成j,因此dp[i][j] |= dp[i-1][j-k*a[i]]  (0=<k<=mi)。用dp求解bool一般很浪費,複雜度比較高,並且資訊很少。

但是如果改變定義方式,用dp[i][j]表示前i個數表示j的時候第i個數最多能剩下幾個,用-1表示即使全用上也無法構成,那麼

dp[i][j]=

  m[i] (如果dp[i-1][j]>=0) 或

  -1(j<a[i] 或者 dp[i][j-a[i]]<=0)

dp[i][j]= dp[i][j-ai]-1 (這裡巧妙運用遞推)      

這樣定義狀態從O(N^2*M)優化到了O(N^2)

[演算法分析]

令S=∑(i*a[i]),若S為奇數,則不可能實現,否則令Mid=S/2,則問題轉化為能否從給定的大理石中選取部分大理石,使其價值和為Mid。

這實際上是母函式問題,用動態規劃求解也是等價的。

m[i, j],0≤i≤6,0≤j≤Mid,表示能否從價值為1..i的大理石中選出部分大理石,使其價值和為j,若能,則用true表示,否則用false表示。則狀態轉移方程為:

m[i, j]=m[i, j]  OR  m[i-1,j-i*k]         (0≤k≤a[i])

規劃的邊界條件為:m[i,0]=true; 0≤i≤6

若m[i, Mid]=true,0≤i≤6,則可以實現題目要求,否則不可能實現。

我們來分析上述演算法的時間效能,上述演算法中每個狀態可能轉移的狀態數為a[i],每次狀態轉移的時間為O(1),而狀態總數是所有值為true的狀態的總數,實際上就是母函式中項的數目。

[演算法優化]

實踐發現:本題在i較小時,由於可選取的大理石的價值品種單一,數量也較少,因此值為true的狀態也較少,但隨著i的增大,大理石價值品種和數量的增多,值為true的狀態也急劇增多,使得規劃過程的速度減慢,影響了演算法的時間效率。

另一方面,我們注意到我們關心的僅是能否得到價值和為Mid的值為true的狀態,那麼,我們能否從兩個方向分別進行規劃,分別求出從價值為1..3的大理石中選出部分大理石所能獲得的所有價值和,和從價值為4..6的大理石中選出部分大理石所能獲得的所有價值和。最後通過判斷兩者中是否存在和為Mid的價值和,由此,可以得出問題的解。

狀態轉移方程改進為:

當i≤3時:

m[i, j]=m[i, j]  OR  m[i-1,j-i*k]         (1≤k≤a[i])

當i>3時:

m[i, j]=m[i, j]  OR  m[i+1,j-i*k]        (1≤k≤a[i])

規劃的邊界條件為:m[i,0]=true; 0≤i≤7

這樣,若存在k,使得m[3,k]=true, m[4,Mid-k]=true,則可以實現題目要求,否則無法實現。

(程式及優化前後的執行結果比較見附件)

從上圖可以看出雙向動態規劃與單向動態規劃在計算的狀態總數上的差異。

回顧本題的優化過程可以發現:本題的實際背景與雙向搜尋的背景十分相似,同樣有龐大的狀態空間,有確定的初始狀態和目標狀態,狀態量都迅速增長,而且可以實現交匯的判斷。因此,由本題的優化過程,我們認識到,雙向擴充套件以減少狀態量的方法不僅適用於搜尋,同樣適用於動態規劃。這種在不同解題方法中,尋找共通的屬性,從而借用相同的優化思想,可以使我們不斷創造出新的方法。

3.2  減少每個狀態轉移的狀態數

在使用動態規劃方法解題時,對當前狀態的計算都是進行一些決策並引用相應的已經計算過的狀態,這個過程稱為“狀態轉移”。因此,每個狀態可能做出的決策數,也就是每個狀態可能轉移的狀態數是決定動態規劃演算法時間複雜度的一個重要因素。本節將討論減少每個狀態可能轉移的狀態數的一些方法。

1、四邊形不等式和決策的單調性

例三、石子合併問題(NOI`95)

[問題描述]

  在一個操場上擺放著一排n(n≤20)堆石子。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的得分。

試程式設計求出將n堆石子合併成一堆的最小得分和最大得分以及相應的合併方案。

[演算法分析]

這道題是動態規劃的經典應用。由於最大得分和最小得分是類似的,所以這裡僅對最小得分進行討論。設n堆石子依次編號為1,2,…..,n。各堆石子數為d[1..n],則動態規劃的狀態表示為:

m[i,j],1≤i≤j≤n,表示合併d[i..j]所得到的最小得分,則狀態轉移方程和邊界條件為:

m[i,j]=0                                   i=j

    i<j (注意這裡k的邊界條件是可以等於j的,不然會漏掉情況)

(後面的d為該次合併所增加的得分,得分為i到j的石子數目的和。可以構造D[i][j]預處理,構造過程可以用D[i][j]=D[i][j-1]+a[j]來優化,複雜度O(n^2)))

同時令s[i,j]=k,表示合併的斷開位置,便於在計算出最優值後構造出最優解。

上式中的計算,可在預處理時計算,i=1..n;t[0]=0, 則:

上述演算法的狀態總數為O(n2),每個狀態轉移的狀態數為O(n),每次狀態轉移的時間為O(1),所以總的時間複雜度為O(n3)。

[演算法優化]

四邊形不等式的定義:

如果對於任意的a1≤a2<b1≤b2,有m[a1,b1]+m[a2,b2]m[a1,b2]+m[a2,b1],那麼m[i,j]滿足四邊形不等式

當函式w[i,j]滿足w[i’,j]≤w[i,j’] 時稱w關於區間包含關係單調。

解決四邊形不等式問題的大概步驟是:1.證明w滿足四邊形不等式,這裡w是m的附屬量,形如m[i,j]=opt{m[i,k]+m[k,j]+w[i,j]},此時大多要先證明w滿足條件才能進一步證明m滿足條件2.證明m滿足四邊形不等式3.證明s[i,j-1]≤s[i,j]≤s[i+1,j]

對於本題

在石子歸併問題中,令w[i,j]= ,則w[i,j]滿足四邊形不等式,同時由d[i]≥0,t[i]≥0可知w[i,j]滿足單調性。

m[i,j]=0                                i=j

  i<j         …………①

對於滿足四邊形不等式的單調函式w,可推知由遞推式①定義的函式m[i,j]也滿足四邊形不等式,即。這一性質可用數學歸納法證明如下:

我們對四邊形不等式中“長度”l=j’-i進行歸納

當i=i’或j=j’時,不等式顯然成立。由此可知,當l≤1時,函式m滿足四邊形不等式。

下面分兩種情形進行歸納證明:

情形1:i<i’=j<j’

在這種情形下,四邊形不等式簡化為如下的反三角不等式:m[i,j]+m[j,j’] ≤m[i,j’],設k=max{p | m[i,j’]=m[i,p-1]+m[p,j’]+w[i,j’] },再分兩種情形k≤j或k>j。下面只討論k≤j,k>j的情況是類似的。

情形1.1:k≤j,此時:

情形2:i<i’<j<j’

設 y=max{p | m[i’,j]=m[i’,p-1]+m[p,j]+w[i’,j] }

   z=max{p | m[i,j’]=m[i,p-1]+m[p,j’]+w[i,j’] }

仍需再分兩種情形討論,即z≤y或z>y。下面只討論z≤y,z>y的情況是類似的。

由i<z≤y≤j有:

綜上所述,m[i,j]滿足四邊形不等式。

令s[i,j]=max{k | m[i,j]=m[i,k-1]+m[k,j]+w[i,j] }

由函式m[i,j]滿足四邊形不等式可以推出函式s[i,j]的單調性,即

s[i,j]≤s[i,j+1]≤s[i+1,j+1],     i≤j

當i=j時,單調性顯然成立。因此下面只討論i<j的情形。由於對稱性,只要證明s[i,j]≤s[i,j+1]。

令mk[i,j]=m[i,k-1]+m[k,j]+w[i,j]。要證明s[i,j]≤s[i,j+1],只要證明對於所有i<k≤k’≤j且mk’[i,j]≤mk[i,j],有:mk’[i,j+1]≤mk[i,j+1]。

事實上,我們可以證明一個更強的不等式

mk[i,j]-mk’[i,j]≤mk[i,j+1]-mk’[i,j+1]

也就是:           mk[i,j]+mk’[i,j+1]≤mk[i,j+1]+mk’[i,j]

利用遞推定義式將其展開整理可得:m[k,j]+m[k’,j+1]≤m[k’,j]+m[k,j+1],這正是k≤k’≤j<j+1時的四邊形不等式。

綜上所述,當w滿足四邊形不等式時,函式s[i,j]具有單調性。

於是,我們利用s[i,j]的單調性,得到優化的狀態轉移方程為:

m[i,j]=0                                           i=j

     i<j

用類似的方法可以證明,對於最大得分問題,也可採用同樣的優化方法。

改進後的狀態轉移方程所需的計算時間為

(程式及優化前後的執行結果比較見附件)

上述方法利用四邊形不等式推出最優決策的單調性,從而減少每個狀態轉移的狀態數,降低演算法的時間複雜度。

上述方法是具有普遍性的。對於狀態轉移方程與①式類似,且w[i,j]滿足四邊形不等式的動態規劃問題,都可以採用相同的優化方法,如最優二叉排序樹(NOI`96)等。下面再舉一例。

例四、郵局(IOI`2000)

[問題描述]

按照遞增順序給出一條直線上座標互不相同的n個村莊,要求從中選擇p個村莊建立郵局,每個村莊使用離它最近的那個郵局,使得所有村莊到各自所使用的郵局的距離總和最小。

試程式設計計算最小距離和,以及郵局建立方案。

[演算法分析]

本題也是一道動態規劃問題,詳細分析請看文字附件(郵局解題報告)。將n個村莊按座標遞增依次編號為1,2,……,n,各個郵局的座標為d[1..n],狀態表示描述為:m[i,j]表示在前j個村莊建立i個郵局的最小距離和。所以,m[p,n]即為問題的解,且狀態轉移方程和邊界條件為:

m[1,j]=w[1,j]

         i≤j

其中w[i,j]表示在d[i..j]之間建立一個郵局的最小距離和,可以證明,當僅建立一個郵局時,最優解出現在中位數,即設建立郵局的村莊為k,則,於是,我們有:

  ,  

同時,令s[i,j]=k,記錄使用前i-1個郵局的村莊數,便於在算出最小距離和之後構造最優建立方案。

上述演算法中w[i,j]可通過O(n)時間的預處理,在O(1)的時間內算出,所以,該演算法的狀態總數為O(n*p),每個狀態轉移的狀態數為O(n),每次狀態轉移的時間為O(1),該演算法總的時間複雜度為O(p*n2)。

[演算法優化]

本題的狀態轉移方程與①式十分相似,因此我們猜想其決策是否也滿足單調性,即

s[i-1,j]≤s[i,j]≤s[i,j+1]

首先,我們來證明函式w滿足四邊形不等式,即:

 

,下面分為兩種情形,z≤y或z>y,下面僅討論z≤y,z>y的情況是類似的。

由i≤z≤y≤j有:

接著,我們用數學歸納法證明函式m也滿足四邊形不等式。對四邊形不等式中“長度”l=j’-i進行歸納:

當i=i’或j=j’時,不等式顯然成立。由此可知,當l≤1時,函式m滿足四邊形不等式。

下面分兩種情形進行歸納證明:

情形1:i<i’=j<j’,即m[i,j]+m[j,j’] ≤m[i,j’],

設k=max{p | m[i,j’]=m[i,p-1]+m[p,j’]+w[i,j’] },再分兩種情形k≤j或k>j。下面只討論k≤j,k>j的情況是類似的。

情形2:i<i’<j<j’

設  y=max{p | m[i’,j]=m[i’-1,p]+w[p+1,j] }

z=max{p | m[i,j’]=m[i-1,p]+w[p+1,j’] }

仍需再分兩種情形討論,即z≤y或z>y。

情形2.1,當z≤y<j<j’時:

情形2.2,當i-1<i’-1≤y<z<j’時:

最後,我們證明決策s[i,j]滿足單調性。

為討論方便,令mk[i,j]=m[i-1,k]+w[k+1,j];

我們先來證明s[i-1,j]≤s[i,j],只要證明對於所有i≤k<k’<j且mk’[i-1,j]≤mk[i-1,j],有:mk’[i,j]≤mk[i,j]。

類似地,我們可以證明一個更強的不等式

mk[i-1,j]-mk’[i-1,j]≤mk[i,j]-mk’[i,j]

也就是:           mk[i-1,j]+mk’[i,j]≤mk[i,j]+mk’[i-1,j]

利用遞推定義式展開整理的:m[i-2,k]+m[i-1,k’]≤m[i-1,k]+m[i-2,k’],這就是i-2<i-1<k<k’時m的四邊形不等式。

我們再來證明s[i,j]≤s[i,j+1],與上文類似,設k<k’<j,則我們只需證明一個更強的不等式:             mk[i,j]-mk’[i,j]≤mk[i,j+1]-mk’[i,j+1]

也就是:           mk[i,j]+mk’[i,j+1]≤mk[i,j+1]+mk’[i,j]

利用遞推定義式展開整理的:w[k+1,j]+w[k’+1,j+1]≤w[k+1,j+1]+w[k’+1,j],這就是k+1<k’+1<j<j+1時w的四邊形不等式。

綜上所述,該問題的決策s[i,j]具有單調性,於是優化後的狀態轉移方程為:

m[1,j]=w[1,j]

         i≤j

 s[i,j]=k

同上文所述,優化後的演算法時間複雜度為O(n*p)。

(程式及優化前後的執行結果比較見附件)

四邊形不等式優化的實質是對結果的充分利用。它通過分析狀態值之間的特殊關係,推出了最優決策的單調性,從而在計算當前狀態時,利用已經計算過的狀態所做出的最優決策,減少了當前的決策量。這就啟發我們,在應用動態規劃解題時,不僅可以實現狀態值的充分利用,也可以實現最優決策的充分利用。這實際上是從另一個角度實現了“減少冗餘”。

2、決策量的優化

通過分析問題最優解所具備的性質,從而縮小有可能產生最優解的決策集合,也是減少每個狀態可能轉移的狀態數的一種方法。

大家所熟悉的NOI`96中的新增號問題,正是從“所得的和最小”這一原則出發,僅在等分點的附近新增號,從而大大減少了每個狀態轉移的狀態數,降低了演算法的時間複雜度。讓我們在再來看一例。

例五、石子歸併的最大得分問題

[問題描述]

見例三,本例只考慮最大得分問題。

[演算法分析]

設n堆石子依次編號為1,2,…..,n。各堆石子數為d[1..n],則動態規劃的狀態表示為:

m[i,j],1≤i≤j≤n,表示合併d[i..j]所得到的最大得分,則狀態轉移方程和邊界條件為:

m[i,j]=0                                   i=j

    i<j

同時令s[i,j]=k,表示合併的斷開位置,便於在計算出最優值後構造出最優解。

    該演算法的時間複雜度為O(n3)。

[演算法優化]

仔細分析問題,可以發現:s[i,j]要麼等於i+1,要麼等於j,即:

證明可以採用反證法,設使m[i,j]達到最大值的斷開位置為p,且i+1<p<j,,下面分為2種情形討論。

情形1、y≥z

由p<j,可設s[p,j]=k,則相應的合併方式可以表示為:

(  (a[i]…a[p-1])  ( (a[p]...a[k-1]) (a[k]..a[j]) )  )

相應的得分為:…………①

下面考慮另一種合併方案s’[i,j]=k,s’[i,k]=p,表示為:

( ( (a[i]…a[p-1]) (a[p]...a[k-1]) )  (a[k]..a[j])   )

相應的得分為:…………②

由y≥z可得,T<T’,這與使m[i,j]達到最大值的斷開位置為p的假設矛盾。

情形2、y<z  與情形1類似。

於是,狀態轉移方程優化為:

m[i,j]=0                                      i=j

    i<j

優化後每個狀態轉移的狀態數減少為O(1),演算法總的時間複雜度也降為O(n2)。

(程式及優化前後的執行結果比較見附件)

本題的優化過程是通過對問題最優解性質的分析,找出最優決策必須滿足的必要條件,這與搜尋中的最優性剪枝的思想十分類似,由此我們再次看到了相同的優化思想應用於不同的演算法設計方法。同時,我們也認識到:動態規劃的優化必須建立在全面細緻分析問題的基礎上,只有深入分析問題的屬性,挖掘問題的實質,才能實現演算法的優化。

3、合理組織狀態

    在動態規劃求解的過程中,需要不斷地引用已經計算過的狀態。因此,合理地組織已經計算出的狀態有利於提高動態規劃的時間效率。

例六、求最長單調上升子序列

[問題描述]

給出一個由n個數組成的序列x[1..n],找出它的最長單調上升子序列。即求最大的m和a1,a2……,am,使得a1<a2<……<am且x[a1]<x[a2]<……<x[am]。

[演算法分析]

這也是一道動態規劃的經典應用。動態規劃的狀態表示描述為:

m[i],1≤i≤n,表示以x[i]結尾的最長上升子序列的長度,則問題的解為 max{m[i],1≤i≤n},狀態轉移方程和邊界條件為:

m[i]=1+max{0, m[k]|x[k]<x[i] , 1≤k<i }

同時當m[i]>1時,令p[i]=k,表示最優決策,以便在計算出最優值後構造最長單調上升子序列。

上述演算法的狀態總數為O(n),每個狀態轉移的狀態數最多為O(n),每次狀態轉移的時間為O(1),所以演算法總的時間複雜度為O(n2)。

[演算法優化]

我們先來考慮以下兩種情況:

1、若x[i]<x[j],m[i]=m[j],則m[j]這個狀態不必保留。因為,可以由狀態m[j]轉移得到的狀態m[k] (k>j,k>i),必有x[k]>x[j]>x[i],則m[k]也能由m[i]轉移得到;另一方面,可以由狀態m[i]轉移得到的狀態m[k] (k>j,k>i),當x[j]>x[k]>x[i]時,m[k]就無法由m[j]轉移得到。

由此可見,在所有狀態值相同的狀態中,只需保留最後一個元素值最小的那個狀態即可。

2、若x[i]<x[j],m[i]>m[j],則m[j]這個狀態不必保留。因為,可以由狀態m[j]轉移得到的狀態m[k] (k>j,k>i),必有x[k]>x[j]>x[i],則m[k]也能由m[i]轉移得到,而且m[i]>m[j],所以m[k]≥m[i]+1>m[j]+1,則m[j]的狀態轉移是沒有意義的。

綜合上述兩點,我們得出了狀態m[k]需要保留的必要條件:不存在i使得:x[i]<x[k]且m[i]≥m[k]。於是,我們保留的狀態中不存在相同的狀態值,且隨著狀態值的增加,最後一個元素的值也是單調遞增的。

也就是說,設當前保留的狀態集合為S,則S具有以下性質D:

對於任意i∈S, j∈S, i≠j有:m[i]≠m[j],且若m[i]<m[j],則x[i]<x[j],否則x[i]>x[j]。

下面我們來考慮狀態轉移:假設當前已求出m[1..i-1],當前保留的狀態集合為S,下面計算m[i]。

1、若存在狀態k∈S,使得x[k]=x[i],則狀態m[i]必定不需保留,不必計算。因為,不妨設m[i]=m[j]+1,則x[j]<x[i]=x[k],j∈S,j≠k,所以m[j]<m[k],則m[i]=m[j]+1≤m[k],所以狀態m[i]不需保留。

2、否則,m[i]=1+max{m[j]| x[j]<x[i], j∈S}。我們注意到滿足條件的j也滿足x[j]=max{x[k]|x[k]<x[i], k∈S}。同時我們把狀態i加入到S中。

3、若2成立,則我們往S中增加了一個狀態,為了保持S的性質,我們要對S進行維護,若存在狀態k∈S,使得m[i]=m[k],則我們有x[i]<x[k],且x[k]=min{x[j]|x[j]>x[i], j∈S}。於是狀態k應從S中刪去。

於是,我們得到了改進後的演算法:

For i:=1 to n do

{

   找出集合S中的x值不超過x[i]的最大元素k;

   if  x[k]<x[i]  then 

   {

      m[i]:=m[k]+1;

      將狀態i插入集合S;

      找出集合S中的x值大於x[i]的最小元素j;

      if  m[j]=m[i]  then   將狀態j從S中刪去;

   }

}

從性質D和演算法描述可以發現,S實際上是以x值為關鍵字(也是以m值為關鍵字)的有序集合。若使用平衡樹實現有序集合S,則該演算法的時間複雜度為O(n*log2n)。本題優化後,每個狀態轉移的狀態數僅為O(1),而每次狀態轉移的時間變為O(log2n),這也體現了上文所提到的優化中不同因素之間的矛盾,但從總體上看,演算法的時間複雜度是降低了。

(程式及優化前後的執行結果比較見附件)

回顧本題的優化過程,首先通過分析狀態之間的分析,減少需要保留的狀態數,同時發現需要保留狀態的單調性,從而減少了每個狀態可能轉移的狀態數,並通過高效資料結構平衡樹組織當前保留的狀態,實現演算法的優化。

通過對本題的優化,我們認識到減少保留的狀態數,合理組織已經計算出的狀態可以實現減少每個狀態可能轉移的狀態數,同時,選取恰當的資料結構也是演算法優化的一個重要原則,在下文的闡述中,還會看到藉助資料結構實現演算法優化。

4、細化狀態轉移

所謂“細化狀態轉移”,就是將原來的一次狀態轉移細化成若干次狀態轉移,其目的在於減少總的狀態轉移的次數。在優化前,問題的決策一般都是複合決策,也就是一些子決策的排列,因此決策的規模較大,每個狀態可能轉移的狀態數也就較多,優化的方法就是將每個複合決策細化成若干個子決策,並在每個子決策後面增設一個狀態,這樣,後面的子決策只在前面的子決策達到最優解時才進行轉移,因此在優化後,雖然,狀態總數增加了,但是總的狀態轉移次數卻減少了,演算法總的複雜度也就降低了。

應該注意的是:上述優化應該滿足一個條件,即原來每個複合決策的各個子決策之間也滿足最優化原理和無後效性,也就是說:複合最優決策的子決策也是最優決策;前面的子決策不影響後面的子決策。

上述優化方法再一次體現了實現一個因素的優化要以增大另一個因素作為代價,但是,演算法總的時間複雜度的降低才是我們的真正目的。

3.3  減少狀態轉移的時間

我們知道,狀態轉移是動態規劃的基本操作,因此,減少每次狀態轉移所需的時間,對提高演算法的時間效率具有重要的意義。

狀態轉移主要有兩個部分構成:

1.             進行決策:通過當前狀態和選取的決策計算出需要引用的狀態。

2.             計算遞推式:根據遞推式計算當前狀態值。其中主要操作是常數項的計算。

本節將分別討論提高這兩部分時間效率的一些方法。

1、減少決策時間

例七、LOSTCITY (NOI`2000)

[問題描述]

現給出一張單詞表、特定的語法規則和一篇文章:

文章和單詞表中只含26個小寫英文字母a…z。

單詞表中的單詞只有名詞,動詞和輔詞這三種詞性,且相同詞性的單詞互不相同。單詞的長度均不超過20。

語法規則可簡述為:名詞短語:任意個輔詞字首接上一個名詞;動詞短語:任意個輔詞字首接上一個動詞;句子:以名詞短語開頭,名詞短語與動詞短語相間連線而成。

文章的長度不超過1000。且已知文章是由有限個句子組成的,句子只包含有限個單詞。

程式設計將這篇文章劃分成最少的句子,在此前提之下,要求劃分出的單詞數最少。

[演算法分析]

這是也是一道動態規劃問題。我們分別用v,u,a表示動詞,名詞和副詞,給出的文章用L[1..M]表示,則狀態表示描述為:

F(v,i):表示L前i個字元劃分為以動詞結尾(當i<>M時,可帶任意個輔詞字尾)的最優分解方案下劃分的句子數與單詞數;

F(u,i):表示L前i個字元劃分為以名詞結尾(當i<>M時,可帶任意個輔詞字尾)的最優分解方案下劃分的句子數與單詞數。

過去的分解方案僅通過最後一個非輔詞的詞性影響以後的決策,所以這種狀態表示滿足無後效性,

狀態轉移方程為:

F(v,i)=min{ F(n,j)+(0,1), L(j+1..i)為動詞;

        F(v,j)+(0,1), L(j+1..i)為輔詞,i<>M;}

F(n,i)=min{ F(n,j)+(1,1),  L(j+1..i)為名詞;

        F(v,j)+(0,1),  L(j+1..i)為名詞;

        F(n,j)+(0,1),  L(j+1..i)為輔詞,i<>M;}

邊界條件:F(v,0)=(1,0);  F(n,0)=(∞, ∞);

問題的解為:min{ F(v,M), F(u,M) };

上述演算法中,狀態總數為O(M),每個狀態轉移的狀態數最多為20,在進行狀態轉移時,需要查詢L[j+1..i]的詞性,根據其詞性做出相應的決策,並引用相應的狀態。下面就通過不同的方法查詢L[j+1..i]的詞性,比較它們的時間複雜度。

[演算法實現]

    設單詞表的規模為N,首先我們對單詞表進行預處理,將單詞按字典順序排序併合並具有多重詞性的單詞。在查詢詞性時有以下幾種方法:

方法1、採用順序查詢法。最壞情況下需要遍歷整個單詞表,因此最壞情況下的時間複雜度為O(20*N*M),比較次數最多可達1000*5k*20=108,當資料量較大時效率較低。

方法2、採用二分查詢法。最壞情況下的時間複雜度為O(20*M*log2N),最多比較次數降為5k*20*log21000=106,完全可以忍受。

集合查詢最為有效的方法要屬採用雜湊表了。

方法3、採用雜湊表查詢單詞的詞性。首先將字串每四位摺疊相加計算關鍵值k,然後用雙重雜湊法計算雜湊函式值h(k)。採用這種方法,通過O(N)時間的預處理構造雜湊表,每次查詢只需O(1)的時間,因此,演算法的時間複雜度為O(20*M+N)=O(M)。

採用雜湊表是進行集合查詢的一般方法,對於以字串為元素的集合還有更為高效的方法,即採用檢索樹[3]

方法四、採用檢索樹查詢單詞的詞性。由於每個狀態在進行狀態轉移時需要查詢的所有單詞都是分佈在同一條從樹根到葉子的路徑上的,因此,如果選取從樹根走一條路徑到葉子作為基本操作,則每個狀態進行狀態轉移時的最多20次單詞查詢只需O(1)的時間,另外,建立檢索樹需要O(N)的時間,因此,演算法總的時間複雜度雖然仍為O(M),但是由於時間複雜度的常數因子小於方法三,因此實際測試的速度也最快。

(程式及四種方法的執行結果的比較見附件)

從本題的優化過程可以看出:採用正確的資料結構是演算法優化的重要原則,在動態規劃演算法的優化中也同樣適用。方法3使用了雜湊表這一高效的集合查詢資料結構,方法四使用的針對性更強的檢索樹,使得演算法的時間效率得到了提高。

2、減少計算遞推式的時間

計算遞推式的主要操作是對常數項的計算,因此減少計算遞推式所需的時間主要是指減少計算常數項的時間。

例八、公路巡邏 (CTSC`2000)

[問題描述]

在一條沒有分岔的公路上有n(n≤50)個關口,相鄰兩個關口之間的距離都是10km。所有車輛在這條公路上的最低速度為60km/h,最高速度為120km/h,且只能在關口出改變速度。

有m(m≤300)輛巡邏車分別在時刻Ti從第ni個關口出發,勻速行駛到達第ni+1個關口,路上耗費時間為ti秒。

兩輛車相遇指他們之間發生超車現象或同時到達某個關口。

求一輛於6點整從第1個關口出發去第n個關口的車(稱為目標車)最少會與多少輛巡邏車相遇,以及在此情況下到達第n個關口的最早時刻。

假設所有車輛到達關口的時刻都是整秒。

[演算法分析]

本題也是用動態規劃來解。問題的狀態表示描述為:

F(i,T)表示在時刻T到達第i個關口的途中最少已與巡邏車相遇的次數。則狀態轉移方程和邊界條件為:

F(i,T)=min{F(i-1,T-Tk)+w(i-1,T-Tk,T),300≤Tk≤600}   2≤i≤n

邊界條件:F(1,06:00:00)=0;

問題的解為:min{F(n,T)}

其中,函式w(i-1,T-Tk,T)是計算目標車於時刻T-Tk從第i-1個關口出發,於時刻T到達第i個關口,途中與巡邏車相遇的次數。

下面來分析上述演算法的時間複雜度,問題的階段數為n,第i個階段的狀態數為(i-1)*300,則狀態總數為:,每個狀態轉移的狀態數為300,每次狀態轉移所需的時間關鍵取決與函式w的計算。下面比較採用不同的計算方法時,時間複雜度的差異。

[函式w的計算]

方法1、在每個決策中都進行一次計算,對所有從第i個關口出發的巡邏車進行判斷,這樣平均每次狀態轉移的時間為O(1+m/n),由M的最大值為300,演算法總的時間複雜度為:

方法2、仔細觀察狀態轉移方程可以發現,在對狀態F(i,T)進行轉移時,所計算的函式w都是從第i個關口出發的,而且出發時刻都是T,只是相應的到達時刻不同,我們考慮能否找出它們之間的聯絡,從而能夠利用已經得出的結果,減少重複運算。

我們來考慮w(i,T,k)與w(i,T,k+1)之間的聯絡:

對於每輛從第i個關口出發的巡邏車,設其出發時刻和到達時刻分別為Stime和Ttime,則:

若Ttime<k或Ttime>k+1,則目標車A、目標車B與該巡邏車的相遇情況相同;

若Ttime=k,則目標車A與該巡邏車相遇,對目標車B的分析又分為:若Stime≤T,則目標車B不與該巡邏車相遇,否則目標車B也與該巡邏車相遇;

若Ttime=k+1,則目標車B與該巡邏車相遇,對目標車A的分析又分為:若      Stime≥T,則目標車A不與該巡邏車相遇,否則目標車A也與該巡邏車相遇;

我們令⊿[k]=w[i,T,k+1]-w[i,T,k],由上述討論得:

⊿[k]=G((Ttime=k+1) and (Stime≥T)) –G((Ttime=k) and (Stime≤T)).

其中函式G(P)表示所有從i個關口出發,且滿足條件P的巡邏車的數目。

這樣我們就找到了函式w之間的聯絡。於是,我們在對狀態F(i,T)進行轉移時,先對所有從第i個關口出發的巡邏車進行一次掃描,在求出w[i,T,T+300]的同時求出⊿[T+301..T+600],這一步的時間複雜度為O(m/n)。在以後的狀態轉移中,由w[i,T,k+1]=w[i,T,k]+⊿[k],僅需O(1)的時間就可以求出函式值w,狀態轉移時間僅為O(1)。則演算法總的時間複雜度為:

雖然,演算法時間複雜度的階並沒有降低,但由於M的最大值為300,N的最大值為50,所以實際測試中,優化的效果還是十分明顯的。

(程式及兩種方法的執行結果比較見附件)

本題對動態規劃的優化實際上是應用了動態規劃本身的思想,在計算遞推式的常數項時,引進了函式⊿,利用了過去的計算結果,避免了重複計算,消除了“冗餘”,從而提高演算法的時間效率。上文郵局問題中函式w的計算也是通過預處理減少了重複計算,近來新出現的雙重動態規劃也是應用這個思想,利用動態規劃計算遞推式的常數項。可見,這種優化方法是很有普遍性的。

四、結語

本文主要從減少狀態總數,減少每個狀態轉移的狀態數和減少狀態轉移的時間這三個方面討論了對動態規劃時間效率的優化,同時也間接地討論了對一般演算法進行優化的方法。

在優化的過程,我認識到:對演算法的優化一方面要深入分析問題的屬性,挖掘問題的本質,另一方面要從原有演算法的不足之處入手,不斷優化、精益求精。

動態規劃的演算法設計具有很大的靈活性,需要具體模型具體分析。演算法設計如此,演算法優化也是如此,本文所述只是一些一般性的方法,許多優化技巧還需要選手們在平時的訓練比賽中深入挖掘。

動態規劃作為一種高效的演算法,仍有許多優化的餘地。不斷提高演算法的效能,使其適應於更大的規模,我想這是廣大資訊學選手共同的願望,希望大家共同研究探討動態規劃演算法的優化,這也是本文創作的初衷。

參考文獻

[1] 吳文虎、 趙鵬,1993-1996美國計算機程式設計競賽試題與解析,清華大學出版社,1999。

[2] 吳文虎、王建德,國際國內青少年資訊學(計算機)競賽試題解析,清華大學出版社,1997。

[3] 傅清祥、王曉東,演算法與資料結構,電子工業出版社,1998。

[4] 全國青少年資訊學(計算機)奧林匹克分割槽聯賽組織委員會,資訊學奧林匹克(季刊),1999.3,2000.2。

附錄

[1] 這個式子只是直觀描述了動態規劃的時間複雜度的決定因素,並不能作為普遍的計算公式。

[2] 四邊形不等式是Donald E. Knuth從最優二叉搜尋樹的資料結構中提出的,這裡被運用於證明動態規劃中決策的單調性。

[3] 採用檢索樹查詢字串只要從樹根出發走到葉結點即可,需要的時間正比於字串的長度。如果雜湊函式確實是隨機的,那麼雜湊函式的值與字串中的每一個字母都有關係。所以,計算雜湊函式值的時間與檢索樹執行一次運算的時間大致相當。但計算出雜湊函式值後還要處理衝突。因此,一般情況下,在進行字串查詢時,檢索樹比雜湊表省時間。