入門區間DP,第一個問題就是線性的規模小的石子合併問題
dp數組的含義是第i堆到第j堆進行合併的最優值
就是說dp[i][j]可以由dp[i][k]和dp[k+1][j]轉移過來
狀態轉移方程 dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+1][j] + sum[i][j]) 對於第i堆到第j堆合併的花費 他的子問題是第i個的合併順序
op1:k實際上控制的是第i堆也就是起始堆的合併順序 因爲必須是相鄰合併dp[i][i] 先合併dp[i+1][j]最後再來合併第i堆 dp[i][i+1] && dp[i+2][j] 就是分開合併i 和i + 1堆,i+2和j之間所有的堆,最後+tmp作整體合併 ok got it!
/* 入門問題——取石子 每次只能合併相鄰的一堆 求最小花費 dp[i][j] 常常用來表示i到j這個區間的最優值是多少 也就是說dp[i][j]可以由dp[i][k]和dp[k+1][j]轉移過來 狀態轉移方程 dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+1][j] + sum[i][j]) 對於第i堆到第j堆合併的花費 他的子問題是第i個的合併順序 op1:k實際山控制的是第i堆也就是起始堆的合併順序 因爲必須是相鄰合併dp[i][i] 先合併dp[i+1][j]最後再來合併第i堆 dp[i][i+1] && dp[i+2][j] 就是分開合併i 和i + 1堆,i+2和j之間所有的堆,最後+tmp作整體合併 ok got it! */ #include <iostream> #include <cstdio> #include <string.h> #define inf 1e8 + 1e7 using namespace std; const int maxn = 1e3 + 1e1; int dp[maxn][maxn],sum[maxn]; int a[maxn]; //由sum[i][j]表示第i堆石子到第j堆石子全部合併的總石子量。 //dp[i][j]爲第i堆石子到第j堆合併的花費 int getMinval(int n) { memset(dp,0,sizeof(dp)); for(int v = 1;v < n;v++)//第一維:循環控制合併堆的長度從長度2開始 for(int i = 0;i < n-v;i++)//起點 { int j = i + v;//終點 dp[i][j] = inf;//僅僅一次初始化的機會,所以可以在裏面進行初始化 int tmp = sum[j] - (i > 0 ? sum[i - 1] : 0);//求取i-j中合併的最後一次花費 for(int k = i;k < j;k++) dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+1][j] + tmp); } return dp[0][n-1]; } int main() { int n; while(~scanf("%d",&n)) { for(int i = 0;i < n;i++) { scanf("%d",&a[i]); } sum[0] = a[0]; //sum[i] 表示從1-i堆中石子的總個數 for(int i = 1;i < n;i++) sum[i] = sum[i-1] + a[i]; printf("%d\n",getMinval(n)); } return 0; }
數據量稍微大一些的就不能用n3的複雜度來進行計算了
於是我們找到了數學優化公式——四邊形不等式優化法則
先來說結論,優化的地方是對k的尋找,最優值k
如果零s[i][j] 來表示dp[i][j]取得最優值時候的k值,那麼k的取值範圍是{s[i][j-1],s[i+1][j]}
關於證明看了很多博客了,這裏有一篇博客不錯,拿來引用一下
https://blog.csdn.net/NOIAu/article/details/72514812
核心如下
~PART ONE 交叉小於包含 對於( a < b <= c< d ) 如果有f[a][c]+f[b][d]<=f[b][c]+f[a][d] (可以理解爲一句話,交叉小於包含,即交叉的兩個區間,a到c和b到d的值滿足小於等於包含的兩個區間[bc包含於ad]) 則說這個東西滿足四邊形不等式,當然這個東西可能是dp數組,也可以是其他數組,比如引入裏提到的cost數組,表示的是i到j的花費(比如合併石子問題) 給出兩個定理: 1、如果上述的w函數同時滿足區間包含單調性和四邊形不等式性質,那麼函數dp也滿足四邊形不等式性質 我們再定義s(i,j)表示 dp(i,j) 取得最優值時對應的下標(即 i≤k≤j 時,k 處的 dp 值最大,則 s(i,j)=k此時有如下定理 2、假如dp(i,j)滿足四邊形不等式,那麼s(i,j)單調,即 s(i,j)≤s(i,j+1)≤s(i+1,j+1) 如果不知道爲什麼,沒有關係,反正後面都要證明
具證明過程,有興趣的可以去看看上面大佬的博客
dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+1][j] + cost[i][j]}
對於代價數組cost[i][j]要進行證明其符合四邊形不等式:cost[i][j]+cost[i+1][j+1]<=cost[i+1][j]+cost[i][j+1]
對於所有的i,j,令其滿足i< i+1<=j< j+1
我們需要證明
cost[i][j]+cost[i+1][j+1]<=cost[i+1][j]+cost[i][j+1]
移項
cost[i][j]-cost[i+1][j]<=cost[i][j+1]-cost[i+1][j+1]
令f(j)=cost[i][j]-cost[i+1][j]
f(j)=cnt[j]-cnt[i-1]-(cnt[j]-cnt[i])
f(j)=cnt[i]-cnt[i-1]
都跟j無關了,自然一定滿足四邊形不等式(這個時候是直接等於了,但沒有違反四邊形不等式)
同理證明dp數組的滿足性
要推導dp[i][j]的凸性,自然要滿足對任意的i,j,令i< i+1<=j< j+1 有如下結論 dp[i][j]+dp[i+1][j+1]<=dp[i+1][j]+dp[i][j+1] 令dp[i+1][j]取得最優值的時候k=x 令dp[i][j+1]取得最優值的時候k=y 令x < =y(之後還要令x > y,這裏不再贅述,讀者如有興趣可以自行推導,方式相似) 將k=x代入dp[i][j],k=y代入dp[i+1][j+1] 左式=dp[i][x]+dp[x+1][j]+cost[i][j]+dp[i+1][y]+dp[y+1][j+1]+cost[i+1][j+1]① 而對於i< i+1<=j< j+1 由於已經在壹中證明了cost的凸性,所以 cost[i][j]+cost[i+1][j+1]<=cost[i+1][j]+cost[i][j+1]② 我們會發現這個不等式的左邊在①式中出現過,所以把②式中的左式和右式替換一下可以得到如下結論 dp[i][x]+dp[x+1][j]+cost[i][j]+dp[i+1][y]+dp[y+1][j+1]+cost[i+1][j+1] < =
dp[i][x]+dp[x+1][j+1]+cost[i][j+1]+dp[i+1][y]+dp[y+1][j]+cost[i+1][j]
即dp[i][j]+dp[i+1][j+1]<=dp[i][j+1]+dp[i+1][j] 證畢
最後來看決策函數的滿足性(s[i][j] = k)
現在我們已經證明了cost數組和dp數組的凸性,要證明決策單調以證明優化的正確性 即要證明s[i][j-1]<=s[i][j]<=s[i+1][j] 對於s[i][j-1]<=s[i][j] 令dp[i][j-1]取得最小值時的k=y,對於所有x≠y,令x<=y 可以有如下推導 ∵x+1<=y+1<=j-1< j 四邊形不等式有: dp[x+1][j-1]+dp[y+1][j]<=dp[y+1][j-1]+dp[x+1][j]
在式子兩邊同時加上dp[i][x]+cost[i][j-1]+dp[i][y]+cost[i][j] 可以得到
dp[i][x]+dp[x+1][j-1]+cost[i][j-1]+dp[i][y]+dp[y+1][j]+cost[i][j] < = dp[i][x]+dp[x+1][j]+cost[i][j]+dp[i][y]+dp[y+1][j-1]+cost[i][j-1]
dp[i][j-1]+dp[i][j]<=dp[i][j]+dp[i][j-1] (k=x)…………(k=y)……(k=x)……(k=y) 移項
dp[i][j-1]-dp[i][j-1]<=dp[i][j]-dp[i][j] (k=x)…………(k=y)……(k=x)……(k=y)
由於我們是令k=y時dp[i][j-1]取得最小值,那麼dp[i][j-1] (k=x)一定大於等於dp[i][j-1] (k=y),所以左式大於零,所以右式也大於零,所以對於dp[i][j-1]可以取到最優值的y,所有小於它的值,對於dp[i][j]來說,都沒有y優,所以最優決策一定不是小於y的,如果令s[i][j]表示dp[i][j]取得最優值的時候的k值,那麼一定有 s[i][j-1]<=s[i][j] 證畢
所以我們就可以對代碼進行優化,得到如下的解決過程
#include <iostream> #include <cstdio> #include <string.h> #define inf 1e8 + 1e7; using namespace std; const int maxn = 1e4 + 1e1; int dp[maxn][maxn],s[maxn][maxn],cnt[maxn]; int main() { int n; while(~scanf("%d",&n)) { memset(cnt,0,sizeof(cnt)); for(int i = 1;i <= n;i++) { scanf("%d",&cnt[i]); cnt[i] = cnt[i-1] + cnt[i]; } for(int i = 0;i <= n;i++) { dp[i][i] = 0; s[i][i] = i; } for(int i = n;i >= 1;i--)//從上到下進行鋪墊,因爲高決策範圍爲s[i+1][j] { for(int j = i + 1;j <= n;j++)//從下到上進行鋪墊因爲,低決策範圍需要s[i][j-1] { //有過疑問:爲什麼從i+1開始,這不廢話,直線型的dp,i到j的最優值問題,區間問題。。。 int tmp = inf; int te; for(int k = s[i][j-1];k <= s[i+1][j];k++)//取最優決策集合//這樣的性質是這個不等式擁有的,他的最優解就是這樣單調的 { if(tmp > dp[i][k] + dp[k+1][j] + cnt[j] - cnt[i-1]) { tmp = dp[i][k] + dp[k+1][j] + cnt[j] - cnt[i-1]; te = k; } } dp[i][j] = tmp; s[i][j] = te; } } cout<<dp[1][n]<<endl; } return 0; }