终于把斜率优化搞懂了。。。
转移方程的初步推导
首先设置 DP 数组的状态:
设 表示将前
个玩具放进容器的最小花费。
聪明的你很容易就可以得到转移方程:
其中 ,
表示的是玩具长度的前缀和。
但是,这样的做法时间复杂度是 的,那是包会超时的好吧。
考虑 DP 优化
所以还是要优化的。
我们可以把上面那个转移方程展开(可以先不管取 )。
展开过程如下:
为了方便展开 后面那个恶心玩意,我这先令
,
(这样就好多了)。
于是,等式就变成了这样:
我们可以把 看作常数,把
看作未知数。我们先把常数放在一起,未知数放在一起。
把只与 有关的项放到等号左边,把其它项放在等号右边,就变成了这样:
这时,你就会发现等式中含有既有 又有
的项,所以这时不可以用单调队列优化的。
但是,聪明的你也许会觉得这个式子很眼熟。
看着像。。。
于是,我们可以使用 DP 斜率优化!
DP 斜率优化过程
所以,在这道题目中,
问题就变成了:找一个点,让经过这个点的斜率为 的直线的截距最小。
我们可以使用单调队列维护下凸包,然后直接套上 DP 斜率优化的模板就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int l;
int a[50001];
int sum[50001];
int f[50001];
int q[50001];
double dx(int i,int j)
{
int x1=a[i];
int x2=a[j];
if(x1-x2==0)return 1e-9;
return x1-x2;
}
double dy(int i,int j)
{
int y1=f[i]+a[i]*a[i];
int y2=f[j]+a[j]*a[j];
return y1-y2;
}
double slope(int i,int j)
{
return (double)dy(i,j)/dx(i,j);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>l;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=n;i++)
{
a[i]=sum[i]+i;
}
int head=1,tail=0;
for(int i=1;i<=n;i++)
{
while(head<tail&&slope(i-1,q[tail])<=slope(q[tail],q[tail-1]))
tail--;
q[++tail]=i-1;
while(head<tail&&slope(q[head+1],q[head])<=2*(a[i]-(l+1)))
head++;
int j=q[head];
f[i]=f[j]+(a[i]-a[j]-(l+1))*(a[i]-a[j]-(l+1));
}
cout<<f[n];
return 0;
}