HDOJ 3507

来源:互联网 发布:音乐信息编辑软件 编辑:程序博客网 时间:2024/05/29 02:25

第一篇博文,就献给DP的优化问题吧。集训刚开始,就整这么难的说。只好自己去搜解题报告了(感谢各位大牛的贡献)。要用到单调队列的线性性质。

第一次接触这种优化问题,写一篇解题报告记录一下。

——————————————————————————————————————————————————————————

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3507

——————————————————————————————————————————————————————————

题目关键字:DP优化,单调队列,计算几何, 斜率优化

——————————————————————————————————————————————————————————

基本思路:

         如果不考虑时间,那么本题是一道较为简单的DP问题。复杂度显然为O(n^2)。但题目中用红字标注出n的范围,就是要让我们考虑优化问题。

         状态转移方程为:F[i] = min{F[j] + (sum[i] - sum[j])^2 + m}.                 //此处注意技巧:一堆连续数的和可以用两个sum相减表示

         我们将从许多决策中挑选一个最好的作为当前状态的值。考虑有决策 x < y < i 。

         若y 优于x ,F[x] + (sum[i] - sum[x])^2 + m > F[y] + (sum[i] - sum[y])^2 + m  , 解不等式有 k =  (F(x) + sum[x]^2 - F[y] - sum[y]^2)/(sum[x] - sum[y]) <  2 * sum[i] 。

         通过本结论,我们来分析三个决策 x < y < z 的情况。当 k(x,y) > k(y,z)时,分三种情况讨论。第一种,k(x,y)小于2*sum[i] ,则 y优于x ,z优于y,z最优。若k(x,y)大于2*sum[i], k(y,z)小于2*sum[i],则x优于y,z优于y, z和x中有一个最优需要再比较。第三种,k(y,z)大于2*sum[i] , 则x最优。总之与y就没有关系了。

得到【结论1】:有意义的决策们,他们之间的斜率是递增的(我们可以删除无用的点了,该点与i的取值无关,它本身就是无用的) 。

            因为斜率是递增的,于是单调队列这个东东华丽丽的出现了。所有元素都只会进栈一次,出栈一次,所以插入的总体复杂度肯定是线性的。而选择最优的时候,我们总是可以从队头选择,因为在寻找最优的时候,我们可以把前面不如最优的舍去了,即让他们出队(因为sum[i]是递增的,随着i的增加,原来不是最优的将永远不是最优,),这样头指针最多也只会扫一遍,仍为线性的。所以,我们可以用单调队列来维护决策们。

           维护两把指针,头指针指向的总是最优(寻找恰好后一个元素大于2*sum[i]的那个元素),尾指针处插入新的决策点i ,插入时考察是否单调,直到到队头(此处不用再考虑之前删掉的那些了,那些永远不如头指针处的元素好。)

           这样每次首先在头指针处查找最优解,求出F[i]后,在将i维护进单调队列中。

—————————————————————————————————————————————————————————

细节问题:

1. 维护单调队列的时候,要加入t > h这个限制因素,因为很可能一直扫到倒数第二个元素,都没找到符合后一个条件的插入点,这时,如果再继续扫描,很可能发生数组下标越界的现象。

2. 初值问题:初值应该是f[0]而不是f[1], 要考虑将前几个元素一起打包的情况!所以开始时要从 0这个决策点开始找最优解。队头一开始要在0决策点处。

3. (本条仍不是很明确)当斜率的分母等于0的时候。所以遇到除法一定要考虑这个问题。对这个问题我们要进行一定的分析和处理。

    首先先分析产生这个问题的原因:我想可能只会因为 Ci在某几处为0了。。。所以我们取前面的后面的都行。所以就取后面的好了,反正指针早晚要往后走。

4. 处理斜率时要不要处理成浮点型呢?我想应该是不用的。因为我们关心的是斜率之间的相对大小,而不是斜率的具体值。而当斜率大于2*sum[i] 的时候,向下取整即为2*sum[i] ,我们在判断的时候没有取等号,所以这并不影响我们的结果。另外,fk(que[h],que[h+1])<2*sum[i],意味着如果h和h+1处决策是一样的时候,我们取h处的。且由于刚才分析的那些,我们不能把小于号改成小于等于。

—————————————————————————————————————————————————————————

影响AC的问题:

1. 因为是多case,固对h 和 t 赋 初值时一定要在循环里面赋!否则会发生数组下标越界的问题。这个问题卡了我很久没找出原因来。多case时一定要注意各种初值的给定!

2. 要将sum和f设为 long long型。

3. 遇到除法时一定要考虑,是否会有分母为0的情况!!!

这些都是细节问题,但也是非常恼人的。标红以示警惕!

—————————————————————————————————————————————————————————

码了这么多字不容易啊,不过写完这篇解题报告后,思路清晰了很多。对一个东西如果真的弄明白了,那么陈述出来也不是难事了。

另外,求斜率的函数可以起名为: slope

—————————————————————————————————————————————————————————

源代码:

 

#include <stdio.h>#include <stdlib.h>long n = 0,m = 0;long long sum[500010],f[500010];long que[500010];int fk(int i,int j)                           {    unsigned k = 0;    if(sum[i] == sum[j])        return -1;    return (f[j]+sum[j]*sum[j]-f[i]-sum[i]*sum[i])/(sum[j]-sum[i]);}int main(){    int i = 0,j = 0, h = 1,t = 1;    while(scanf("%d%d",&n,&m)!=EOF)    {       sum[0] = 0;       for(i = 1;i<=n;i++)       {          scanf("%lld",&sum[i]);          sum[i] += sum[i-1];          f[i] = 0;       }       h = 0;t = 0;                                            //多case一定要注意!!!!!        f[0] = 0;       que[0] = 0;       for(i = 1;i<=n;i++)       {          while(h<t && fk(que[h],que[h+1])<2*sum[i])              h++;          j = que[h];          f[i] = f[j] + (sum[i]-sum[j])*(sum[i]-sum[j]) + m;          while(t>h && fk(que[t],i)<fk(que[t-1],que[t])) t--;          que[++t] = i;       }       printf("%lld\n",f[n]);    }    return 0;}


 

原创粉丝点击