斜率优化
斜率优化是用于优化一些线性DP,主要思想类似于凸包。
下面来看一个例题:HDU 3507
由题意不难想到是DP,也很容易退出转移过程
f[i]=min(f[j]+(s[i]−s[j])2)+M(i∈[0,i−1])
但是一看
n
的范围马上就萎了,下面来看看用斜率怎么优化。
现将推出的转移方程化简:
显然
s[i]2
对于当前状态
i
来说是个定值,所以移出来
下面来看如何将它看成一条直线
设
x=s[j],y=f[j]+s[j]2,k=2s[i](注意这里的k一定要和当前状态i有关)
那么
b=−kx+y
y=kx+b
min(b)
就是我们要的最小值。
既然已经转化为直线,那么就我们就是要拿一条斜率固定的直线在一个有许多点的平面内寻找交点,
如果要使截距
b
最小,那么肯定是这条直线肯定与平面的点所形成的的下凸壳相交,
所以在寻找答案的同时我们还要维护一个下凸壳。
再回到例题中,因为题目中的斜率为
对比图会发现,随斜率的增长,直线与下凸壳的交点是不断向
x
轴的正方向移动,
这用一个指针标记就可以了。
效率显然是O(n)的。
拓展
若跳出这道题去看斜率优化,会出现许多情况。
如果从转移方程中推出k不满足递增,那么就要二分在下凸壳上寻找。
如果连
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=500005;
LL s[maxn],f[maxn];
struct jz{
LL x,y;
jz(LL a=0,LL b=0): x(a),y(b) {};
jz operator-(jz &b){return jz(x-b.x,y-b.y);}
}a[maxn];
int n,m,til,now;
inline int _read(){
int num=0;char ch=getchar();
while (ch<'0'||ch>'9') ch=getchar();
while (ch>='0'&&ch<='9') num=num*10+ch-48,ch=getchar();
return num;
}
LL getb(jz x,LL k){return -k*x.x+x.y;}
LL check(jz x,jz y){return x.x*y.y-x.y*y.x;}
int main(){
freopen("exam.in","r",stdin);
freopen("exam.out","w",stdout);
while (scanf("%d%d",&n,&m)==2){
for (int i=1;i<=n;i++) s[i]=s[i-1]+_read();
memset(f,63,sizeof(f));
f[0]=0;til=now=1;a[1]=jz(0,0);
for (int i=1;i<=n;i++){
LL k=2*s[i];
while (now<til&&getb(a[now+1],k)<getb(a[now],k)) now++;
f[i]=getb(a[now],k)+m+s[i]*s[i];
jz x(s[i],f[i]+s[i]*s[i]);
while (til>1&&check(a[til]-a[til-1],x-a[til-1])<=0) til--;
now=min(til,now);a[++til]=x;
}
printf("%lld\n",f[n]);
}
return 0;
}