區間 DP是指在一段區間上進行的一系列動態規划。 對於區間 DP 這一類問題,我們需要計算區間 [1,n] 的答案,通常用一個二維數組 dp 表示,其中 dp[x][y] 表示區間 [x,y]。 有些題目,dp[l][r] 由 dp[l][r−1] 與 dp[l+1][r] 推得;也有些題目,我們需要枚舉區間 [l,r] 內的中間點,由兩個子問題合並得到,也可以說 dp[l][r] 由 dp[l][k] 與 dp[k+1][r] 推得,其中 l≤k<r。 對於長度為 n 的區間 DP,我們可以先計算 [1,1],[2,2]…[n,n] 的答案,再計算 [1,2],[2,3]…[n−1,n],以此類推,直到得到原問題的答案。
一道經典例題:
NOI 1995 石子合並
題目描述
在一個圓形操場的四周擺放N堆石子,現要將石子有次序地合並成一堆.規定每次只能選相鄰的2堆合並成新的一堆,並將新的一堆的石子數,記為該次合並的得分。
試設計出1個算法,計算出將N堆石子合並成1堆的最小得分和最大得分.
輸入輸出格式
輸入格式:數據的第1行試正整數N,1≤N≤100,表示有N堆石子.第2行有N個數,分別表示每堆石子的個數.
輸出格式:輸出共2行,第1行為最小得分,第2行為最大得分.
輸入輸出樣例
4 4 5 9 4
43 54
分析
我們利用動態規划的思想,將問題不斷分為子問題,為了求出最終最小的代價,我們只要求兩堆的最小代價,再求出三堆的最小代價,以此類推得出最終的最小代價。
我們用 dp[i][j] 來表示合並 i 到 j 區間里的石子的最小代價。然后我們寫出下面的公式:
dp[i][j]=min(dp[i][k]+dp[k+1][j])+sum[j]−sum[i−1]
顯然通過這個式子,我們可以按區間長度從小到大的順序進行枚舉來不斷讓石子進行合並,最終就能獲得合並成一堆石子的最小代價。
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int MAXN = 500 + 10; 4 int n, stone[2*MAXN], mi[2*MAXN][2*MAXN], mx[2*MAXN][2*MAXN], s[2*MAXN]; 5 int main() 6 { 7 cin >> n; 8 for (int i = 1; i <= n; i++) 9 cin >> stone[i], stone[i+n] = stone[i]; 10 for (int i = 1; i <= 2*n; i++) 11 s[i] = s[i-1] + stone[i]; 12 for (int i = 2*n-1; i >= 1; i--) 13 { 14 for (int j = i+1; j < n+i; j++) 15 { 16 mi[i][j] = 0x3F3F3F3F; 17 for (int k = i; k < j; k++) 18 { 19 mi[i][j] = min(mi[i][j], mi[i][k]+mi[k+1][j]+s[j]-s[i-1]); 20 mx[i][j] = max(mx[i][j], mx[i][k]+mx[k+1][j]+s[j]-s[i-1]); 21 } 22 } 23 } 24 int ans1 = 0x3F3F3F3f, ans2 = 0; 25 for (int i = 1; i <= n; i++) 26 ans1 = min(ans1, mi[i][i+n-1]), ans2 = max(ans2,mx[i][i+n-1]); 27 cout << ans1 << endl << ans2 << endl; 28 return 0; 29 }