题面:Luogu3195 BZOJ1010
本来以为斜率优化是个什么高级东西。。。这题入门之后……
发现也没什么难的吧
O(n2)
做法:
f[i]
表示选完1~i个物品所花最小花费
转移:
f[i]=min(f[j]+(i−j−1+s[i]−s[j]−L)2)
s[i]
表示从1~i的
c[i]
之和
O(n)
做法:
我们考虑怎么把上面的
O(n)
的转移时间优化到
O(1)
显然,如果
f[i]
从j转移过来比从k转移过来优的话,要满足
f[j]+(i−j−1+s[i]−s[j]−L)2<f[k]+(i−k−1+s[i]−s[k]−L)2
其中 (k<j<i)
我们把 s[i] 搞成 s[i]+i , L 加上1,那么
化开来是:
f[j]−2∗s[i]∗(s[j]−L)+(s[j]−L)2<f[k]−2∗s[i]∗(s[k]−L)+(s[k]−L)2
(f[j]+(s[j]−L)2)−(f[k]+(s[k]−L)2)<2∗s[i]∗(s[j]−s[k])
同除以 2∗(s[j]−s[k]) 得:
(f[j]+(s[j]−L)2)−(f[k]+(s[k]−L)2)2∗(s[j]−s[k])<s[i]
(f[j]+(s[j]−L)2)−(f[k]+(s[k]−L)2)2∗s[j]−2∗s[k]<s[i]
然后发现这个不等式化成了一个斜率不等式 x(j)−x(k)y(j)−y(k)<s[i]
所以我们可以斜率优化这个dp,其实就是维护一个下凸壳
每次把斜率大于s[i]的最小答案来转移,最后把不是下凸壳的节点删掉
用单调队列维护一下就好了
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <ctime>
#include <map>
#include <queue>
#include <cstdlib>
#include <string>
#include <climits>
#include <set>
#include <vector>
#define int long long
using namespace std;
int n,L,a[1000001],q[1000001],s[1000001],f[1000001]={0};
inline int sqr(int x){return x*x;}
inline double check(int x,int y){
return (double)((f[x]+sqr(s[x]+L)-f[y]-sqr(s[y]+L))/(2.0*(s[x]-s[y])));
}
signed main()
{
scanf("%lld%lld",&n,&L);L++;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
a[i]+=a[i-1];s[i]=a[i]+i;
}
int l=1,r=1;q[1]=0;
for(int i=1;i<=n;i++){
while(l<r&&check(q[l+1],q[l])<s[i]+1.0)l++;
f[i]=f[q[l]]+sqr(s[i]-s[q[l]]-L);
while(l<r&&check(q[r],q[r-1])>check(i,q[r]))r--;
q[++r]=i;
}
printf("%lld",f[n]);
return 0;
}