Renatus的博客

不管到了什么时候,人们最缺少的都是时间

斜率优化DP学习笔记

斜率优化DP学习笔记


RT,最近练习一些斜率优化的DP,也发现了不少的问题,下面进行一下小小的记录与知识的回顾与理解

介绍

首先,斜率优化是一种对DP进行优化的好东西,大多数的时候,它的使用可以把O(n2)的复杂度降低到O(nlogn)甚至是O(n),提升的效果十分明显,已经渐渐地成为一种特定的题目类型

原理

它的原理,基于数形结合的思想。有时候,我们会遇到一些DP题目,而这些题目有一个特点,就是它的决策从前面已经求出来DP值的状态(即前驱状态)中进行转移(其实这句话和废话一样)然后,我们发现它们的表达式是这样的形式:

F(now)=F(pre)+K(pre)+A

其中F(now)是现在正在考虑的状态,F(pre)是它的前驱状态,K(pre)是一个只与K有关的函数,而A是一个常数

为了知道我们选取哪一个状态进行计算结果最优,我们可以做如下的作差比较,考虑前驱状态x,y我们进行作差:

F(x)+K(x)+A(F(y)+K(y)+A)

=F(x)+K(x)F(y)K(y) (*)

如果x要比y更优,则我们会知道(*)式>0,即:

F(x)F(y)>K(y)K(x)

不妨令x>y,则我们可以这样(其实往往我们可以证明出K函数的单调性,或是它含有单调的因式,这样我们可以放心地把它除过去,这里不妨设K(x)<K(y)):

(F(x)F(y))/(K(y)K(x))>1

这时,我们类比解析几何里两点斜率的定义,可以这样进行点的创建:

上述式子表示的就是A(K(x),F(x))B(K(y),F(y))的连线的斜率

当然上述式子比较狭义,广义的来说是可以通过各种手段来做到这一步就可以:

(F(x)+K(x)F(y)K(y))/(G(x)G(y))>A

其中K(i)G(i)函数是只与i有关的函数,然后,我们可以通过用单调栈的方法维护上凸壳或下凸壳来进行快速的检索,可以使用双指针,即再用一个指针记录当前最优解的位置(此时要求最优解的单调性),此时复杂度为O(n),或二分查找,此时复杂度为O(nlogn),那么它的原理就这样结束啦O(∩_∩)O~~

应用

下面贴几道例题,当然由于我非常弱,所以持续更新

BZOJ 1010: [HNOI2008]玩具装箱toy

经典题,没有什么好说的,采用上文所述的广义的方法即可,丑陋的代码如下

#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define maxn 50005
#define LL long long int
#define A (LL)(2*i+2*pre[i]-2*l-2)
#define EFF (LL)(i-op.idx+pre[i]-pre[op.idx]-l-1)*(i-op.idx+pre[i]-pre[op.idx]-l-1)
#define set_y(k) (LL)(dp[k]+(k+pre[k])*(k+pre[k]))
#define set_x(k) (LL)(k+pre[k])
#define slope(i,j) (double)(i.y-j.y)/(i.x-j.x)
using namespace std;
struct point{
    LL x,y,idx;
    void read(LL x,LL y,LL idx){
        this->x=x,this->y=y,this->idx=idx;
    }
}stack[maxn];
LL n,l,top=0,now=0;
LL len[maxn],dp[maxn],pre[maxn];
void DP(){
    dp[0]=0;
    stack[0].read(0,0,0);
    for(LL i=1;i<=n;i++){
        point op=stack[now];
        while(top>0){
            if(now<top&&slope(stack[now],stack[now+1])<A){
                op=stack[++now];
                continue;
            }
            break;
        }
        dp[i]=dp[op.idx]+EFF;
        point next;
        next.read(set_x(i),set_y(i),i);
        while(top>0){
            op=stack[top];
            if(top>0&&slope(op,next)<slope(stack[top-1],op)){
                top--;
                if(now>top)now--;
                continue;
            }
            break;
        }
        stack[++top]=next;
    }
    printf("%lld",dp[n]);
}

int main(){
    /*freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);*/
    scanf("%lld%lld",&n,&l);
    for(LL i=1;i<=n;i++){
        scanf("%lld",&len[i]);
    }
    pre[1]=len[1];
    for(LL i=2;i<=n;i++){
        pre[i]=pre[i-1]+len[i]; 
    }
    DP();
    return 0;
}

细节

1.最好在单调栈中最开始加一个虚拟点(原点),处理起来更方便,同时数组从1开始编号,有可能溢出时使用long long int类型

2.要看好维护的是上凸壳还是下凸壳,这两种的判断符号不同,而且最优解指针要注意不能越界

3.要输出最终解。。。

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Goseqh/article/details/59123030
文章标签: 斜率优化DP
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

斜率优化DP学习笔记

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭