區間DP(總結)


  學長一晚上的耐心講解,使我明白區間DP這么高級的東西,還是挺容易的。也就是在一段區間內的動態規划。

  下面用例題進行總結。

  例題:石子歸並。

  描述 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子並成為一堆。合並的過程只能每次將相鄰的兩堆石子堆成一堆,並將新的一堆石子數記為該次合並的得分。

  給組測試數據 

     輸入   4    表示有4堆石子

       4 4 5 9  表示每堆石子的個數

     輸出  54         表示最大的得分   

  分析:主要思想就是一個一個累加:4 4 5 9 先看下去是我想知道dp[i][j]的最大值,i表示起始位置,j表示終止位置,所以我肯定是想求出dp[1][4]間的最大值但是我從1到4可是如圖這三種截取方法,所以我先從小的開始記錄。

dp[1][1]=4;dp[2][2]=4;dp[3][3]=5;dp[4][4]=9。然后我在長度為2的時候記錄,dp[1][2]=4+4=8,dp[2][3]=8+5=14;dp[3][4]=14+9=23;這樣加起來的值就是8+14+23=45;但是如果我反過來呢?dp[1][2]=5+9=14;dp[2][3]=14+4=18;dp[3][4]=18+4=22;這樣加起來的值就是14+18+22=54。很明顯,54就是所求的最大值。

如圖,如果我相求圈着的這個三個的值,我完全可以有圖上這兩種分割,並且分割出來的值是已經知道的了。

動態規划的思想:先兩兩合並,在三三合並,最后再NN合並,在合並過程中,較大的區間可以拆分成已經的小區間進行計算,省時又省力。比如,我可以在三三合並的時候可以拆分成一一還有二三相加。即把當前階段的合並方法細分成前一階段已計算出的方法,選擇其中的最優方案。

具體來說我們應該定義一個數組dp[i,j]用來表示合並方法,i表示從編號為i的石頭開始合並,j表示所求區間的結尾,sum表示的是石頭的數量。

 

對於上面的例子來說,

   第一階段:dp[1][1],dp[2][2],dp[3][3],dp[4][4] 因為一開始還沒有合並,所以這些值應該全部為0。

    第二階段:兩兩合並過程如下,其中sum(i,j)表示石頭的數量,即從i開始數j個數的和

 

              dp[1,2]=dp[1,1]+dp[2,2]+sum[1,2];

     dp[2,3]=dp[2,2]+dp[3,3]+sum[2,3];

     dp[3,4]=dp[3,3]+dp[4,4]+sum[4,4];

 

 

 

    第三階段:三三合並可以拆成兩兩合並,拆分方法有兩種,前兩個為一組或后兩個為一組

 

         dp[1,3]=dp[1,2]+dp[3,3]+sum[1,3]或dp[1,3]=dp[1,1]+dp[2,3]+sum[1,3];取其最優

    dp[2,4]=dp[2,2]+dp[3,4]+sun[2,4]或dp[2,4]=dp[2,3]+dp[3,3]+sum[2,4];取其最優

    第四階段:四四合並的拆分方法用三種,同理求出三種分法的得分,取其最優即可。以后第五階段、第六階段依次類推,最后在第六階段中找出最優答案即可。

   動態方程為dp[i][j]=dp[i][k]+dp[k+1][j]+sum[i][j];

 參考代碼。

 

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 //#define MAX 999999
 8 
 9 int main ()
10 {
11     int dp[210][210],sum[210][210],a[210];
12     int n;
13     while(~scanf("%d",&n))
14     {
15         //memset(dp,MAX,sizeof(dp));
16         for (int i=1; i<=n; i++)
17             scanf("%d",&a[i]);
18         for (int i=1; i<=n; i++)
19         {
20             dp[i][i]=0;//初始化為0
21             sum[i][i]=a[i];//將每堆石子的個數賦值進來
22         }
23         for (int len=1; len<n; len++)//按長度從小到大枚舉
24         {
25             for (int i=1; i<=n&&i+len<=n; i++)//i表示開始位置
26             {
27                 int j=len+i;                    //j表示長度為len的一段區間的結束位置
28                 for (int k=i; k<j; k++)         //用k來表示分割區間
29                 {
30                     sum[i][j]=sum[i][k]+sum[k+1][j];
31                     if (dp[i][j]<dp[i][k]+dp[k+1][j]+sum[i][j])
32                         dp[i][j]=dp[i][k]+dp[k+1][j]+sum[i][j];
33                     //cout<<i<<" "<<j<<" "<<sum[i][j]<<" "<<k<<" "<<dp[i][j]<<endl;
34                 }
35             }
36         }
37         cout<<dp[1][n]<<endl;
38     }
39     return 0;
40 }

 http://acm.nyist.net/JudgeOnline/problem.php?pid=737

這是一道類似的題目,和上述代碼有點區別,把7,15,31行修改一下即可哦~


免責聲明!

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



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