石子歸併(區間dp & 四邊形不等式優化)
第1行:N(2 <= N <= 100) 第2 - N + 1:N堆石子的數量(1 <= A[i] <= 10000)
輸出最小合併代價Input示例
4 1 2 3 4Output示例
19
好經典的一個區間dp的題啊!不過這次在紙上畫了畫,自己都想的差不多了,但是自己只寫了個O(n^3)的演算法,後來看了看,發現還有一個用四邊形不等式優化的方法,也學習了學習,時間優化到了O(n^2)。
好了不廢話了,第一次寫一個型別題的時候,都會仔細的寫一篇講解的:
要求是把n堆石子合併成一堆,但是隻能合併相鄰的,所以不能用貪心的方法了。
然後我們考慮簡化問題的方法,即:把n-1堆石子合併成一堆求最少耗費,然後再和剩下的一堆合併。
然後再次簡化:把n-2堆石子合併成一堆,再和剩下的2堆合併。
……
……
……
最後問題就成了,把2堆石子合併。
然後我們再開始正序往後想,兩堆石子的合併想必都會,就是從1 ~ n-1 依次列舉。
那麼三堆呢?
比如 1 2 3 三堆,我們可以分開,考慮:dp[1][3] = min(dp[1][2] + dp[3][3] , dp[1][1] + dp[2][3]) + sum[1,3]
那麼四堆呢?
我們再分割槽間 [1,1]+[2,3] 和 [1,2] + [3,4] 和 [1,3] + [4,4] 最小值加上一個sum[1,4]就是移動這四堆所需要的最小耗費。
現在再看是不是清晰了點,那麼我我們先看n^3的方法:
三個for:
①列舉長度l:2 ~ n
②列舉起始端點:1 ~ n - l + 1
③列舉斷點位置(就是上面的把區間拆開的點的位置):i ~ endd(endd為末端點,等於i + l - 1)
程式碼如下:(O(n^3))(下面還有優化的演算法)
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
int main()
{
int n;
int t;
int dp[111][111];
int sum[111];
sum[0] = 0;
scanf ("%d",&n);
for (int i = 1 ; i <= n ; i++)
scanf ("%d",&t) , sum[i] = sum[i-1] + t;
for (int i = 1 ; i <= n ; i++)
dp[i][i] = 0;
for (int l = 2 ; l <= n ; l++) //選取區間長度
{
for (int i = 1 ; i <= n-l+1 ; i++)
{
int endd = i + l - 1; //區間末端點
dp[i][endd] = INF;
for (int k = i+1 ; k <= endd ; k++)
dp[i][endd] = min(dp[i][endd] , dp[i][k-1] + dp[k][endd] + sum[endd] - sum[i-1]);
}
}
printf ("%d\n",dp[1][n]);
return 0;
}
知道了基本的,然後我們來談優化,優化其實很好想,每次我們找斷點k的時候,都是從i ~ endd一個個列舉。我們是否可以用一個方法快速找到呢?答案是可以的,這裡有一個四邊形不等式:點選開啟連結
也就是說,我們用一個數組s[][]記錄從 i 到 j 斷點的位置,那麼下一輪,我們就找這兩個斷點中間的點:
s[i][j-1] <= k <= s[i+1][j] (注意,這兩個s的數值在算 l - 1 的時候已經算出來了)
這個時候這個k就基本確定在那一兩個數中了。時間複雜度成功優化,當然多開了一個s陣列佔用了空間複雜度,但是是沒辦法的事,本來就是矛盾嘛。
程式碼如下:
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
int main()
{
int n;
int t;
int dp[111][111];
int s[111][111];
int sum[111];
sum[0] = 0;
scanf ("%d",&n);
for (int i = 1 ; i <= n ; i++)
{
scanf ("%d",&t);
sum[i] = sum[i-1] + t;
s[i][i] = i;
}
CLR(dp,0);
for (int l = 2 ; l <= n ; l++) //選取區間長度
{
for (int i = 1 ; i <= n-l+1 ; i++)
{
int endd = i + l - 1; //區間末端點
dp[i][endd] = INF;
for (int k = s[i][endd-1] ; k <= s[i+1][endd] ; k++) //利用四邊形不等式優化範圍
{
if (dp[i][endd] > dp[i][k] + dp[k+1][endd] + sum[endd] - sum[i-1]) //就算這裡k+1超了也不怕
{
dp[i][endd] = dp[i][k] + dp[k+1][endd] + sum[endd] - sum[i-1];
s[i][endd] = k;
}
}
}
}
printf ("%d\n",dp[1][n]);
return 0;
}