题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1022
基准时间限制:1 秒 空间限制:131072 KB 分值: 160
难度:6级算法题
收藏
关注
N堆石子摆成一个环。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。
例如: 1 2 3 4,有不少合并方法
1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)
1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)
1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)
括号里面为总代价可以看出,第一种方法的代价最低,现在给出n堆石子的数量,计算最小合并代价。
Input
第1行:N(2 <= N <= 1000) 第2 - N + 1:N堆石子的数量(1 <= A[i] <= 10000)
Output
输出最小合并代价
Input示例
4 1 2 3 4
Output示例
19
题解:
1. 一开始想到的是:dp[l][r] = min(dp[l][k]+dp[k+1][r]+w[l][r]), l<=k<r 的区间DP,但时间复杂度为O(n^3),而n<=1e3,所以无法通过。
2. 然后需要四边形不等式进行优化,四边形不等式优化DP的结论请看:
传送门
3. 优化后:dp[l][r] = min(dp[l][k]+dp[k+1][r]+w[l][r]), s[l][r-1]<=s[l][r]<=s[l+1][r],即s[l][r-1]<=k<=s[l+1][r]
w[][]数组需要满足满足的条件:
1) 区间包含单调性:被包含的区间的值小于等于包含的区间的值。
2) 四边形不等式:交叉区间之和小于等于被包含区间加包含区间的值
代码如下:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <vector> 6 #include <cmath> 7 #include <queue> 8 #include <stack> 9 #include <map> 10 #include <string> 11 #include <set> 12 using namespace std; 13 typedef long long LL; 14 const int INF = 2e9; 15 const LL LNF = 9e18; 16 const int MOD = 1e9+7; 17 const int MAXN = 2e3+10; 18 19 int dp[MAXN][MAXN], sum[MAXN], w[MAXN][MAXN], s[MAXN][MAXN]; 20 int main() 21 { 22 int n; 23 while(scanf("%d", &n)!=EOF) 24 { 25 sum[0] = 0; 26 for(int i = 1; i<=n; i++) //因为初始时为环(而非一行),所以复制多一份到后面, 27 scanf("%d",&sum[i]), sum[n+i] = sum[i]; 28 29 for(int i = 1; i<2*n; i++) //求前缀和 30 sum[i] += sum[i-1]; 31 32 for(int l = 1; l<2*n; l++) //求出每一段区间,最后一次合并所花费的代价(必为质量和) 33 for(int r = l; r<2*n; r++) 34 w[l][r] = sum[r]-sum[l-1]; 35 36 for(int i = 1; i<2*n; i++) //初始化单位区间 37 dp[i][i] = 0, s[i][i] = i; 38 39 for(int len = 2; len<=n; len++) //递推 40 for(int l = 1; l+len-1<2*n; l++) 41 { 42 int r = l+len-1; 43 dp[l][r] = INF; 44 for(int k = s[l][r-1]; k<=s[l+1][r]; k++) 45 if(dp[l][r]>dp[l][k]+dp[k+1][r]+w[l][r]) //四边形不等式优化 46 { 47 dp[l][r] = dp[l][k]+dp[k+1][r]+w[l][r]; 48 s[l][r] = k; 49 } 50 } 51 int ans = INF; 52 for(int l = 1; l<=n; l++) //枚举起点,取最小值 53 ans = min(ans, dp[l][l+n-1]); 54 printf("%d\n", ans); 55 } 56 }