题意:长度为(N(N<=10^6))的序列,对于点i,要么花费(a[i])放置守卫塔,要么花费(j-i)不放置守卫塔(其中j是点i右侧第一个守卫塔).显然第(N)个点只能放置守卫塔.求最小总花费.
分析:从前往后找太麻烦了,直接倒过来就行,即第一个点必须防止守卫塔,不放置守卫塔的点必须要在它的左边(前面)找到守卫塔.
设(f[i])表示第i个点放置守卫塔的最小总花费,则有
(f[i]=min(f[j]+a[i]+sum_{k=j+1}^{i-1}k-j))
其实后面那个累加式可以预处理出来(sum[i-(j+1)]),其中(sum[i]=sum[i-1]+i=frac{(i+1)*i}{2}),但是为了能够斜率优化,我们得把它拆成含有乘积的式子.
(sum_{k=j+1}^{i-1}k-j=sum[i-1]-sum[j]-(i-j-1)*j),(sum)数组含义与上面相同.
故(f[i]=f[j]+a[i]+sum[i-1]-sum[j]-(i-j-1)*j)
设(k<j)且j比k更优,即
(f[j]+a[i]+sum[i-1]-sum[j]-(i-j-1)*j<f[k]+a[i]+sum[i-1]-sum[k]-(i-k-1)*k)
整理一下上式得到,
(frac{f[j]+j+j^2-sum[j]-f[k]-k-k^2+sum[k]}{j-k}<i)
就可以进行斜率优化了.
但是还有一个超级容易被忽视的地方就是本题不能直接输出(f[n]),因为(n)点不一定会放置守卫塔.
#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;
}
const int N=1000005;
int a[N],q[N];LL f[N],sum[N];
inline double Count(int j,int k){return 1.0*(f[j]+1ll*j*j+j-sum[j]-f[k]-1ll*k*k-k+sum[k])/(double)(j-k);}//j*j可能会爆int
int main(){
int n=read();
for(int i=1;i<=n;++i){
a[n-i+1]=read();
sum[i]=sum[i-1]+i;
}
int l=1,r=1;f[1]=a[1];q[1]=1;
for(int i=2;i<=n;++i){
while(l<r&&Count(q[l+1],q[l])<=i)l++;
f[i]=f[q[l]]+a[i]+sum[i-q[l]-1];
while(l<r&&Count(q[r],q[r-1])>=Count(i,q[r]))r--;
q[++r]=i;
}
for(int i=1;i<=n;++i)f[n]=min(f[n],f[i]+sum[n-i]);//这里超级容易忽略掉
printf("%lld
",f[n]);
return 0;
}