斜率优化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
然后就没什么了