斜率优化 学习笔记

前言:寒假Yousiki讲过斜率优化,但完全没有听懂。现在文化课解析几何也学了不少,终于能做一些题了。

-----------------

有时候我们列出DP方程会得到形如这样的式子:

$f[i]=max/min{f[j]+(a[i]-b[j])^2}+w[i](1\leq j<i)$(其实平不平方都随便啦)这个式子复杂度是$O(n^2)$的。

我们变换一下形式:

$2*a[i]*b[j]+f[i]-a[i]^2-w[i]=f[j]+b[j]^2$

仔细一看,上面的等式可以化成$y=kx+b$的形式,即含$j$的项视为$x$和$y$,含$i$的项视为常数项。

根据这个式子,我们可以将转移优化成$O(n)$的。

根据$y=kx+b$的形式,我们可以视为平面上有许多点,坐标为$(b[j],f[j]+b[j]^2)$。这些点连起来可以形成一个凸包。我们用单调队列来维护这个凸包。

假设$k=2*a[i]>0$,即维护下凸包,斜率是单调递增的。下面的讲述可以用线性规划来理解(人教版高中数学必修二)。

1.如果经过某两点直线的斜率小于现在直线的斜率,那么$head++$。因为我们现在的直线肯定是想经过凸包的边界的,而现在的答案肯定不是最优。

2.现在的队头就是最优解。把$q[head]$带到原方程中。

3.如果经过$q[tail]$和$q[tail-1]$的直线的斜率大于经过$q[tail-1]$和$i$的斜率,那么$tail--$。因为凸包肯定是要囊括所有点的,不能让有的点在凸包外面。

于是转移被优化成$O(n)$的了。

怎么样?是不是很简单?滑稽

其实学长教我们的时候推荐我们用叉积来判断关系,因为会有些极端数据卡掉斜率(比如斜率不存在)。但大部分题都不卡斜率这种做法。

-------------------------------

例题 【HNOI2008】玩具装箱

题目链接

斜率优化入门题。

设$f[i]$表示考虑前$i$个玩具所花费的最小费用,很容易得出方程$f[i]=\min{f[j]+((i-j-1-L)+\sum\limits_{k=i-j+1}^i c[k])^2}$

前缀和优化,变换一下形式:$f[i]=\min(f[j]+(sum[i]-sum[j]+i-j-1-L)^2)$

设$a[i]=sum[i]+i$,$b[i]=sum[i]+i+L+1$

则$f[i]=\min(f[j]+(a[i]-b[j])^2)$

化成$y=kx+b$的形式:$2*a[i]*b[j]+f[i]-a[i]^2=f[j]+b[j]^2$

然后斜率优化一下就可以了。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[50005],n,L,x;
double sum[50005];
int Q[50005],head,tail;
double a(int x) {return sum[x]+x;}
double b(int x){return a(x)+L+1;}
double X(int x){return b(x);}
double Y(int x){return dp[x]+b(x)*b(x);}
double slope(int i,int j){return (Y(i)-Y(j))/(X(i)-X(j));}
signed main()
{
    scanf("%d%d",&n,&L);
    for(int i=1;i<=n;i++){
        scanf("%lf",&sum[i]);
        sum[i]+=sum[i-1];
    }
    head=tail=1;
    for(int i=1;i<=n;i++){
        while(head<tail&&slope(Q[head],Q[head+1])<2*a(i)) ++head;
        dp[i]=dp[Q[head]]+(a(i)-b(Q[head]))*(a(i)-b(Q[head]));
        while(head<tail&&slope(i,Q[tail-1])<slope(Q[tail-1],Q[tail])) --tail;
        Q[++tail]=i;
    }
    printf("%lld",dp[n]);
    return 0;
}

 

posted @ 2020-07-15 10:18  我亦如此向往  阅读(244)  评论(0编辑  收藏  举报