斜率优化dp

斜率优化dp的思想是数形结合,将各种决策点反映在平面直角坐标系中,然后通过斜率进行优化

做法
首先将这道题的$n^2$的dp 算法写出来
然后将其暴力展开
如:f(i)=min(f(j)+(s[i]-s[j]+i-j-1-l)^2)
令s[i]=s[i]+i,l=l+1
原式变为$f(i)=min(f(j)+(s[i]-s[j]-l)^2)
暴力展开后是长这样的:f(i)=f(j)+s[i]^2+s[j]^2+l^2-2s[i]s[j]-2s[i]l+2s[j]l
我们可以将这个式子的右边转化为这种形式:只和j有关 和i,j有关 只和i有关 以及常数项
f(i)=f(j)+s[j]^2+2s[j]l+2s[i]s[j]+s[i]^2-2s[i][l]+l^2
现在我们可以玄学地令:
b=f[i]
y=f(j)+s[j]^2+2ls[j]
k=-2s[i]
x=s[j]
这样这个式子就转换为了:
b=y-k*x+一堆常数项$
因为当循环到i的时候 s[i]的值是固定的 所以s[i]也可以看做是定值
然后你会发现(常数项不用管,反正是常数嘛对不对,不会变的)
y=kx+b 撒花!(但不是结尾撒花)
这时候就可以用我们可爱的斜率优化了
斜率优化的注释在代码里面 因为没有图所以只能可怜的在代码里面打注释了(玩具装箱)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long 
#define y(T) (f[T]+s[T]*s[T]+2*s[T])
#define x(T) (s[T])
#define k(T) (2*s[T]-2*l)
using namespace std;
const int maxn=50000;
ll n,l;
ll cost[maxn],s[maxn],head,tail,f[maxn];
ll q[maxn];
inline double K(ll cxk,ll nmxl)
{
return 1.0*(y(nmxl)-y(cxk))/(x(nmxl)-x(cxk));
}

signed main()
{
scanf("%lld %lld",&n,&l);
for(register ll i=1;i<=n;i++)
{
scanf("%lld",&cost[i]);
s[i]=s[i-1]+cost[i];
}
for(register ll i=0;i<=n;i++)
s[i]+=i;
head=1;tail=1;//初始化都为1 表示严格包含head和tail区间这样可以表示已经有一个点0在队列里面了 而斜率优化dp必须在一开始的时候压一个点0进去才能保证第一段选出来
for(register ll i=1;i<=n;i++)
{
while(head<tail && K(q[head],q[head+1])<k(i))    //注意此处head<tail因为必须有两个元素
head++;//维护头,显然q[head+1]不是最优解(以后也不会是)弹掉
f[i]=f[q[head]]+(s[i]-s[q[head]]-1-l)*(s[i]-s[q[head]]-1-l);
while(head<tail && K(q[tail],i)<=K(q[tail-1],q[tail]))    
tail--; //遇到凹包 需要将再队列中的点斜率大于新加入的点全部弹掉
q[++tail]=i;
}
printf("%lld",f[n]);
return 0;
} 

 

emm总而言之斜率优化的难点就在于把$n^2$算法的dp写出来之后要进行归纳,神奇的令,以及对队列tail的维护与遇到凹包的时候如何处理
最毒瘤的是初始化以及边界条件 head=1 tail=1 head<tail
然后就没什么了

posted on 2019-10-10 21:36  萌德真帅  阅读(212)  评论(0编辑  收藏  举报