HDU3507 Print Article(经典斜率优化dp)

一道很老的斜率优化dp

斜率优化看上去很难,其实是有技巧的 。

对于dp题目,如果你想优化他,一定要先列出朴素的表达式并观察性质

对于本题我们可以发现,如果要更新dp[i],我们就要从前面找到dp[j]+(s[i]-s[j])^2+m的最小值,其中s是前缀和

我们就可以猜测,一定有很多不可能转移的内容,我们应该如何删除它从而降低复杂度。

那么我们假设k<j,当i出现之后,k就不可能作为答案,那么这些k在i处满足的性质就是

dp[j]+(s[i]-s[j])^2+m<=dp[k]+(s[i]-s[k])^2+m

对这个式子进行化简移向,我们就可以得到一个式子

dp[j]+s[j]^2-(dp[k]+s[k]^2)/(2*(s[j]-s[k]))<=s[i]

为什么我们会想到这样移向?因为常见的优化就只有几种,例如四边形优化,单调队列优化,斜率优化,这个初始的式子中有两个变量,还是成对出现的

我们就可以猜测可以表达成斜率,当然这也是一种经验。

所以满足这个情况的k是不满足的当前i的

其次,我们注意到随着i的增长,s[i]是越来越大的,因此这个k永远都不会用到。

这是从头部删除,我们知道,斜率优化还有从尾部删除的,这些都有一种特定的公式,就是倒数第二条斜率比倒数第一条要大,也叫做凸包优化。

我们肯定想不出这样的技巧,但是前人的经验告诉我们这个就是斜率优化的凸包优化的技巧,所以我们应当记住这个结论,并对每个题分析证明这个结论在该题也成立

下面我们来分析,我们不妨记上面的公式为g[k,j]

对于新加入的i,如果g[k,j]>g[j,i]我们需要证明j永远用不到

1.假设g[j,i]<=s[i],根据我们上面推出来的公式,j可以删除

2.g[j,i]>=s[i],那么g[k,j]>s[i]j依旧不会成为答案,因为k比j更适合

所以我们成功证明

下面就是套斜率板子就行。

本题题解借鉴了kuangbin的题解(思路都是kuangbin老师的),我写下为了自我学习,也为了给刚学习斜率优化的同学一点斜率的技巧。

#include<cstdio>
#include<iostream>
#include<string>
#include<queue>
using namespace std;
const int N=500010;
int dp[N];
int q[N];
int sum[N];
int hh,tt,n,m;
int cal(int i,int j){
    return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);
}
int check(int j,int k){
    return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]);
}
int get(int j,int  k){
    return 2*(sum[j]-sum[k]);
}
int main(){

    while(cin>>n>>m){
        for(int i=1;i<=n;i++)
           scanf("%d",&sum[i]);
        sum[0]=dp[0]=0;
        for(int i=1;i<=n;i++)
           sum[i]+=sum[i-1];
        hh=tt=0;
        q[0]=0;
        for(int i=1;i<=n;i++){
            while(hh+1<=tt&&check(q[hh+1],q[hh])<=sum[i]*get(q[hh+1],q[hh]))
               hh++;
            dp[i]=cal(i,q[hh]);
            while(hh+1<=tt&&check(i,q[tt])*get(q[tt],q[tt-1])<=check(q[tt],q[tt-1])*get(i,q[tt]))
                    tt--;
            q[++tt]=i;
        }
        printf("%d\n",dp[n]);
    }
    return 0;
}
View Code

 

posted @ 2020-02-05 19:53  朝暮不思  阅读(174)  评论(0编辑  收藏  举报