區間DP模板題
區間DP模板Code:
for(int len=2;len<=n;len++) { for(int i=1;i<=2*n-1;i++) //區間左端點 { int j = i + len - 1; //區間右端點 for(int k=i;k<j;k++) //斷點位置 { f[i][j] = min(f[i][j],f[i][k] + f[k + 1][j] + s[j] - s[i - 1]); } } }
題目描述
在一個園形操場的四周擺放N堆石子,現要將石子有次序地合併成一堆.規定每次只能選相鄰的2堆合併成新的一堆,並將新的一堆的石子數,記爲該次合併的得分。
試設計出1個算法,計算出將N堆石子合併成1堆的最小得分和最大得分.
輸入輸出格式
輸入格式:
數據的第1行試正整數N,1≤N≤100,表示有N堆石子.第2行有N個數,分別表示每堆石子的個數.
輸出格式:
輸出共2行,第1行爲最小得分,第2行爲最大得分.
輸入輸出樣例
輸入樣例#1: 4 4 5 9 4
輸出樣例#1: 43 54
這個題的數據存儲特點有一點代表性:破環成列,把長度爲n的環轉換爲長度爲2n-1的列,再進行一次動歸。
針對於這個題的n很小,我們就可以用它來代表區間長度,這樣O(n ^ 3)也能跑過去了
區間DP的概念就是把一個區間的狀態一直分割爲它的子區間的狀態,一直到這個子區間的狀態是顯然可求的,最後再將它們綜合起來
舉個栗子:
f[i][j]中我們可以將[i,j]這一個區間劃分爲[i,k]和[k + 1,j]這兩個區間的總狀態再進行一次操作
這個的邊界就是[i,k]和[k + 1,j]是顯然可求的狀態
這個題要求一個最大值和最小值的問題
我們可以顯而易見地發現最小值一定小於等於最大值
這樣我們可以只建立一個數組先求最小再求最大,節省了兩個數組的空間(雖然這不是重點qwq)
Code:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int f[300][300]; //節省空間 int s[300]; int n,x,ans = 55312725; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&x); s[i] = s[i - 1] + x; s[i + n] = s[i]; //把長度開到 2n - 1 } for(int i=1;i<n;i++) s[i + n] += s[n]; memset(f,10,sizeof(f)); //初始化 for(int i=1;i<=2*n-1;i++) f[i][i] = 0; for(int len=2;len<=n;len++) { for(int i=1;i<=2*n-1;i++) //區間左端點 { int j = i + len - 1; //區間右端點 for(int k=i;k<j;k++) //斷點位置 { f[i][j] = min(f[i][j],f[i][k] + f[k + 1][j] + s[j] - s[i - 1]); } } } for(int i=1;i<=n;i++) ans = min(ans,f[i][i + n - 1]); printf("%d\n",ans); //最小值一定比最大值要小,所以無需更新 for(int len=2;len<=n;len++) { for(int i=1;i<=2*n-1;i++) //區間左端點 { int j = i + len - 1; //區間右端點 for(int k=i;k<j;k++) //斷點位置 { f[i][j] = max(f[i][j],f[i][k] + f[k + 1][j] + s[j] - s[i - 1]); } } } for(int i=1;i<=n;i++) ans = max(ans,f[i][i + n - 1]); printf("%d\n",ans); return 0; }