石子归并 3

来源:互联网 发布:vb 打开网页 编辑:程序博客网 时间:2024/05/18 22:09

题目描述 Description
有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1]。问安排怎样的合并顺序,能够使得总合并代价达到最小。

输入描述 Input Description
第一行一个整数n(n<=3000)

第二行n个整数w1,w2…wn (wi <= 3000)

输出描述 Output Description
一个整数表示最小合并代价

样例输入 Sample Input
4

4 1 1 4

样例输出 Sample Output
18

数据范围及提示 Data Size & Hint
数据范围相比“石子归并” 扩大了

我有话说:
这道题明确的说明了数据范围的增大,所以用O(n^3)的算法是没有办法通过的,5000ms左右吧。所以要进行优化。方法是平行四边形优化。
关于平行四边形优化,引用网上的部分资料:

   因为在动态规划中,有这样的一类问题

状态转移方程 dp[i][j]=min{dp[i][k-1]+dp[k][j]}+w[i][j] k>i&&k<=j 时间复杂度为 O(n*n*n)
且有如下一些定义和定理:
如果一个函数w[i][j],满足 w[i][j]+w[i’][j’]<=w[i][j’]+w[i’][j] i<=i’<=j<=j’ 则称w满足凸四边形不等式
如果一个函数w[i][j],满足 w[i’][j]<=w[i][j’] i<=i’<=j<=j’ 则称w关于区间包含关系单调

定理1:如果w同时满足四边形不等式和区间单调关系,则dp也满足四边形不等式

定理2:如果定理1条件满足时让dp[i][j]取最小值的k为K[i][j],则K[i][j-1]<=K[i][j]<=K[i+1][j]
注:定理2是四边形不等式优化的关键所在,它说明了决策具有单调性,然后我们可以据此来缩小决策枚举的区间,进行优化

定理3:w为凸当且仅当 w[i][j]+w[i+1][j+1]<=w[i+1][j]+w[i][j+1]

几点说明:
1:定理1的证明比较烦躁,详细的可以见《动态规划算法的优化技巧》毛子青 大神的论文

2:定理3其实告诉我们验证w是否为凸的方法,就是固定一个变量,然后看成是一个一元函数,进而判断单调性。
如,我们可以固定j算出w[i][j+1]-w[i][j]关于i的表达式,看它是关于i递增还是递减,如果是递减,则w为凸

3:实际操作中,我们往往并不需要进行烦躁的证明,而只需要打表,然后观察就行了
如w[i][j],dp[i][j]是否满足四边形不等式啊,w[i][j]是否单调啊,决策函数K[i][j]是否满足定理2的不等式关系啊,都可以通过打表来搞

=====================分割线=====================
最有代价用d[i,j]表示
d[i,j]=min{d[i,k-1]+d[k+1,j]}+w[i,j]
其中w[i,j]=sum[i,j]
四边形不等式
w[a,c]+w[b,d]<=w[b,c]+wa,d 就称其满足凸四边形不等式
决策单调性
w[i,j]<=w[i’,j’] ([i,j]属于[i’,j’]) 既 i’<=i

#include <cstdio>#include <cstring>#include <algorithm>#define N 3100int dp[N][N],sum[N],s[N][N];int main(){    int n;    scanf("%d",&n);        int a[N];sum[0]=0;        memset(s,0,sizeof(s));        for(int i=1;i<=n;i++){            scanf("%d",&a[i]);            s[i][i]=i;            sum[i]=sum[i-1]+a[i];        }        memset(dp,0,sizeof(dp));        int i,j,l,k;        for(l = 2; l <= n; ++l)        {            for(i = 1; i <= n - l + 1; ++i)            {                j = i + l - 1;                dp[i][j] = 2100000000;                for(k = s[i][j-1]; k <= s[i+1][j]; ++k)                {                    if(dp[i][j]>dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1])                    {                        dp[i][j]=dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1];                        s[i][j]=k;//个人理解就是把合并成的石子堆最小代价给记录下来。不知大神们是否认同。                    }                }            }        }        printf("%d\n", dp[1][n]);    return 0;}
0 0
原创粉丝点击