很多情况下,动态规划的转移方程都可以被优化,而斜率优化通常能将算法复杂度降低一个维度。

从一道例题开始

HDU 3507 Print Article

题目大意:给定$n$,$m$,输出序列$n$个数,每连续输出代价为连续输出的数字和的平方加上$m$.

定义

$sum_{pq}=\sum_{k=p+1}^qa_k=pre[q]-pre[p]$,其中$pre[]$维护的是前缀和.

朴素dp

定义状态$dp[i]$ 表示输出前$i$ 个数所需要的最小代价。

转移方程为 $dp[i]=min { dp[q]+sum_{qi}+m }(q<i)$。

如此复杂度为$O(n^2)$,故需要优化。

斜率优化dp

设$p<q$,若从$q$转移而来比从$p$更优,则有$dp[q]+sum_{qi}+m<dp[p]+sum_{pi}+m$,

化简可得,$\frac{(dp[q]+pre[q]^2)-(dp[p]+pre[p]^2)}{2pre[q]-2pre[p]}<pre[i]$ 。

定义映射$f$,使得$k\xrightarrow{f} (2pre[k],dp[k]+pre[k]^2)$,$g[p,q]$为$f(p)$和$f(q)$两点的斜率,

则有,若$g[p,q]<pre[i]$,则$q$比$p$更优。

当$p<k<q$且$g[p,k]>g[k,q]$时,不难证明$k$一定不为最优:

当$g[k,q]<pre[i]$时,$q$比$k$更优;当$g[k,q]\geqslant pre[i]$时,有$g[p,k]>pre[i]$,故$p$比$k$更优。

考虑$pre[i]$数列的单调性,我们只需维护一个斜率递增的队列即可.

斜率优化_barriery

代码如下:

#include <cstdio>
#define N 500005
using namespace std;
typedef long long ll;
int n,m,dp[N],a[N],pre[N],deq[N],r,f;
int y(int n){return dp[n]+pre[n]*pre[n];}
int x(int n){return 2*pre[n];}
int main(void){
    while(~scanf("%d%d",&n,&m)){
        r=-1,f=0;
        deq[++r]=0;
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
            pre[i]=pre[i-1]+a[i];
        }
        for(int i=1;i<=n;++i){
            while(r-f>0){
                if(y(deq[f+1])-y(deq[f])
                   <=pre[i]*(x(deq[f+1])-x(deq[f])))
                    f++;
                else break;
            }
            dp[i]=dp[deq[f]]+(pre[i]-pre[deq[f]])*(pre[i]-pre[deq[f]])+m;
            while(r-f>0){
                if((y(deq[r])-y(deq[r-1]))*(x(i)-x(deq[r]))
                    >=(y(i)-y(deq[r]))*(x(deq[r])-x(deq[r-1])))
                    r--;
                else break;
            }
            deq[++r]=i;
        }
        printf("%d\n",dp[n]);
    }
}