因为NOIP到来开始狂补算法,不知道要不要考斜率DP优化。
PS:以下出现sum表示前缀和
斜率优化主要优化与线性DP,一般的线性DP,转移方程:
f[i]=min(f[j]+sum[i]−sum[j])
f[i]=min(f[j]+a[j]+a[i])
这类线性DPi和j都可以分开计算,所以直接使用线段树,单调栈/单调队列,堆等数据结构快速优化,但是也有这么一类转移方程,i和j都有相乘这类关系,如:
f[i]=min(f[j]+(sum[i]−sum[j])2)+M)
这种方程就不能用普通的方法优化了,需要用到斜率优化。
PS:假设这里的数都为正数,即sum数组递增
如果对于i转移的时候,有j比k更优 k<j<i ,则
f[j]+(sum[i]−sum[j])2+M<f[k]+(sum[i]−sum[k])2+M
f[j]+sum[j]2−2sum[i]∗sum[j]<f[k]+sum[k]2−2sum[i]∗sum[k]
f[j]+sum[j]2−(f[k]+sum[k]2)<2sum[i]∗(sum[j]−sum[k])
f[j]+sum[j]2−(f[k]+sum[k]2)2∗sum[j]−2∗sum[k]<sum[i]
令 X(i)=2sum[i],Y(i)=f[i]+sum[i]2 ,则
Y(j)−Y(k)X(j)−X(k)<sum[i]
所以……这就是斜率呀。
又令 K(i,j)=Y(j)−Y(i)X(j)−X(i) ,则 K(k,j)<sum[i]
所以对于i来说j比k优秀,那么我们可以在求f[i]的时候干掉k了,因为k不会再次优秀。
接下来还可以推一个结论 K(k,j)≥K(j,i) → j 不会或没必要出现在最优解中,为什么呢?
1.
K(j,i)<sum[i]
,那么对 i 来说, i 比 j 优秀,由于sum递增,所以 i 将一直比 j 优秀。
2.
K(j,i)≥sum[i]
,那么 K(k,j)≥K(j,i)≥sum[i] ,即对 i 来说, k 比 j 优秀,由于sum递增,所以 k 将一直比 j 优秀。
所以队列中的候选最优解一定满足 K(que[i−1],que[i])<K(que[i],que[i+1])(hed<i<ti) ,即斜率递增。
所以最后到底怎么做:
1.判断
K(que[hed],que[hed+1])<sum[i]
,有的话hed++;
2.que[hed]是最优政策,求f[i]
3.判断
K(que[til−1],que[til])>=K(que[til],i)
,有的话til–;
4.
que[++til]=i;
真的有这道题,HDU 3507 HDU貌似挂了,挂个vjudge的链接
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;
int n,m,que[500005];
LL sum[500005],f[500005];
inline void readi(int &x){
x=0; char ch=getchar();
while ('0'>ch||ch>'9') ch=getchar();
while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
LL getX(const int i,const int j){return 2*sum[j]-2*sum[i];}
LL getY(const int i,const int j){return f[j]+sum[j]*sum[j]-f[i]-sum[i]*sum[i];}
void _work(){
int hed=1,til=1;
memset(f,63,sizeof(f)); f[0]=sum[0]=que[1]=0;
for (int i=1,x;i<=n;i++){
readi(x); sum[i]=sum[i-1]+x;
while (hed<til&&getY(que[hed],que[hed+1])<getX(que[hed],que[hed+1])*sum[i]) hed++;
f[i]=f[que[hed]]+(sum[i]-sum[que[hed]])*(sum[i]-sum[que[hed]])+m;
while (hed<til&&getY(que[til-1],que[til])*getX(que[til],i)>=getX(que[til-1],que[til])*getY(que[til],i)) til--;
que[++til]=i;
}
printf("%lld\n",f[n]);
}
int main()
{
freopen("printer.in","r",stdin);
freopen("printer.out","w",stdout);
while (scanf("%d%d",&n,&m)==2) _work();
return 0;
}
注意
K(k,j)<sum[i]
,其中右边的sum[i]这个值是保证递增的,这样才可以直接用单调序列,如果不递增……
二分?splay?cdq分治?反正都不会用