斜率优化

这篇文章使用markdown 和 latex 写成。博客园对markdown的支持不是太完善,若是显示异常,请刷新页面php


斜率优化动态规划

之前写过一篇关于动态规划斜率优化的文章,可是很是很差懂T_T,这两天作了一些斜率优化的题,再总结一下:node

例题:HDU 3507

首先这个题朴素的DP方程是这样的:
\(f_i=min(f_j+\sum_{k=j+1}^i(cost_k)+M\)
若是咱们记\(cost\)的前缀和为\(s\),那么
$f_i=min(f_j+(s_i-s_j)^2)+M $
化简获得:
\(f_i=min(f_j+s_i^2+s_j^2-2s_is_j)+M\)
注意到\(s_i\)只与\(f_i\)有关,因此能够从括号内提出
\(f_i=min(f_j+s_j^2-2s_is_j)+M+s_i^2\)
因此决策的表达式就是
\(f_j+s_j^2-2s_is_j\)ios


如今咱们考虑任意两个决策点\(a\)\(b\)(也就是说\(j=a\)\(j=b\)的状况)
假设\(a < b\)
那么决策\(a\)比决策\(b\)更优的条件就是
\(f_a+s_a^2-2s_is_a < f_b+s_b^2-2s_is_b\)
整理获得
\((f_a+s_a^2)-(f_b+s_b^2) < 2s_i(s_a-s_b)\)
继续整理,获得markdown

\[\frac{(f_a+s_a^2)-(f_b+s_b^2)}{s_a-s_b} < 2s_i\]优化

这就是决策\(a\)比决策\(b\)更优的条件
仔细观察这个式子,若是咱们把\(f_a+s_a^2\)\(f_b+s_b^2\)分别看作点A和B的纵坐标,\(s_a\)\(s_b\)看作点A和B的横坐标,那么不等式左面就能够当作是一个斜率式。斜率优化的名字就由此而来。
下文中咱们就把不等式左面记作\(k_{a,b}\)spa


咱们再来考虑三个决策\(a,b,c(a < b < c)\).
若是有 \(k_{a,b} < k_{b,c}\) 那么意味着什么呢?
判断两个决策谁更优须要和\(s_i\)比较,咱们分三种状况讨论:code

  • \(k_{a,b} < k_{b,c} < s_i\)
    由于\(k_{a,b} < s_i\),因此决策\(a\)决策\(b\)更好
    由于\(k_{b,c} < s_i\),因此决策\(b\)决策\(c\)更好
    综上咱们在\(a,b,c\)中咱们应该选择决策\(a\)
  • \(s_i < k_{a,b} < k_{b,c}\)
    这种状况下决策\(b\)比决策\(a\)好,决策\(c\)比决策\(b\)好,因此咱们应该选择决策\(c\)
  • $ k_{a,b} < s_i < k_{b,c}$
    这种状况下决策\(a\)比决策\(b\)好,决策\(c\)也比决策\(b\)好,虽然咱们不能肯定决策\(a\)和决策\(c\)谁更好,可是确定能肯定决策\(b\)是很差的,不用考虑决策\(b\)

经过以上三种状况,咱们发现只要有 \(k_{a,b} < k_{b,c}\),决策\(b\)就必定不是最好的,不用考虑了
基于这一点,咱们有效地减小了须要考虑的决策数,从而对这类DP进行了优化。队列


那么具体怎么实现呢?get

若是咱们把各个决策以点\((s_j,f_j+s_j^2)\)的形式画在平面上,而且对于任意三个点A,B,C(按照横坐标A < B < C)都保证\(k_{a,b} < k_{b,c}\) 不成立(换句话说咱们删去全部使得\(k_{a,b} < k_{b,c}\)的B点),那么咱们就会发现剩下的图形是一个凸多边形。博客

也就是说,若是把各个决策当作是点,咱们实际上要维护的是这些点的凸包。

具体实现的时候,分两种状况:

1.像这道题同样,决策点是依次出现的(横坐标依次增大),那么就很是简单了:

咱们维护一个单调队列,每次算完一个\(f\)的值,就把其对应的决策点加入单调队列队尾,把这个决策点看作是C,若是单调队列中有两个点或以上,就把最后的两个点看作是A和B,若是\(k_{a,b} < k_{b,c}\)那么就把单调队列中最后一个点删去,直到\(k_{a,b} < k_{b,c}\) 不成立为止,加入这个新的决策点
每次须要计算\(f_i\)值的时候,首先维护一下队头,若是队列中有两个或以上元素,且第一个点和第二个点的斜率值 $ < s_i\(的话,就删去队头。*由于\)s_i\(是递增的,如今\) < s_i\(之后确定\) < s_{i+1}$,因此这样维护是合理的。*

这样维护事后,直接取队头的决策就是当前最优的决策。

若是你想不通为何队头就是最优决策的话,画一张图看看。由于维护过的图形是一个上凸包,因此全部的斜率都是随着横坐标的增大而递减的,又由于第一个斜率$ < s_i\(,因此全部的斜率都\) < s_i\(。那么从队头开始每一个点都优于他后面一个点(由于这个斜率\) < s_i$),根据传递性队头的点就是最优的了。

这种状况下状态数仍然是\(O(n)\)的,但转移的时间复杂度从\(O(n)\)降低到\(O(1)\)。又由于单调队列均摊下俩是\(O(1)\)的,因此总体时间复杂度就从\(O(n^2)\)优化到了\(O(n)\)

2.若是各个决策点出现的顺序是无须的,好比bzoj1492,那么就不能简单的用一个单调队列维护了,咱们须要一颗平衡树,每次找到决策须要插入的位置,并分别向左向右维护凸包。这变成了一个经典的动态凸包问题。固然,这个题有其余不用斜率优化的更好的作法。


以上就是斜率优化的所有原理和实现方法。附上hdu3507的代码以供参考:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <iostream>

#define MAXN (500000+10)

using namespace std;
int s[MAXN],a[MAXN],f[MAXN];
struct node{
    int x,y,ss;
    node(int x=0,int y=0,int ss=0):x(x),y(y),ss(ss) {}
};
struct Mono_queue{
    node t[MAXN];
    int f,r;
    int size;
    void init(){
        memset(t,0,sizeof t);
        f=0;
        r=0;
        size=0;
    }
    void push(node x){
        while (size>=2){
            if ((x.x-t[r-1].x >0 && t[r-1].x-t[r-2].x>0)  || (x.x-t[r-1].x <0 && t[r-1].x-t[r-2].x<0)){
                if ((x.y-t[r-1].y)*(t[r-1].x-t[r-2].x)<=(t[r-1].y-t[r-2].y)*(x.x-t[r-1].x)) r-- , size--;
                else break;
            }else {
                if ((x.y-t[r-1].y)*(t[r-1].x-t[r-2].x)>=(t[r-1].y-t[r-2].y)*(x.x-t[r-1].x)) r-- , size--;
                else break;
            }
        }
        t[r++]=x;
        size++;
    }
    void maintain(int x){

        while (size>=2){
            if ((t[f+1].x-t[f].x)>0){
                if ((t[f+1].y-t[f].y)<=x*(t[f+1].x-t[f].x)) f++ , size--;
                else break;
            }else{
                if ((t[f+1].y-t[f].y)>=x*(t[f+1].x-t[f].x)) f++ , size--;
                else break;
            }
        }
    }
    int top(){
        return t[f].ss;
    }
}T;
int main (int argc, char *argv[])
{
    int m,n;
    while (scanf("%d%d",&n,&m)!=EOF){
        T.init();
        memset(f,0,sizeof f);
        for (int i=1;i<=n;i++)  scanf("%d",&a[i]);
        for (int i=1;i<=n;i++)  s[i]=s[i-1]+a[i];
        T.push(node(0,0,0));
        for (int i=1;i<=n;i++){
            T.maintain(2*s[i]);
            int ss=T.top(); 
            f[i]=f[ss]+s[ss]*s[ss]-2*s[ss]*s[i]+s[i]*s[i]+m;
            T.push(node(s[i],f[i]+s[i]*s[i],i));
        }
        printf("%d\n",f[n]);
    }
    return 0;
}

斜率优化的题目都是大同小异,DP 的形式都差很少,只要能整理成斜率式,就能斜率优化。若是变量分离不开,那就不能斜率优化了。

相关文章
相关标签/搜索