您的位置:首页 > 其它

[bzoj 1911] [Apio2010]特别行动队

2017-05-20 15:48 176 查看

[bzoj 1911] [Apio2010]特别行动队

Description



Input



Output



Sample Input

4
-1 10 -20
2 2 3 4

Sample Output

9

HINT



几天前的考试题,当初十分naive地想到用单调队列做,保存f[j]的最大值,然而忘了看清大括号是套在最外面的.后面就只能写暴力,考完后就开始学斜率优化,看了很多人的题解和博客,总算有点感觉了.

首先dp方程是十分naive地就能推出来的:

\[{ f }_{ i }=max\{ { f }_{ j }+{ a\sum _{ k=j+1 }^{ i }{ { A }_{ k } } }^{ 2 }+b\sum _{ k=j+1 }^{ i }{ { A }_{ k } } +c\} \]

至于大坦克(滑稽)就用前缀和处理一下,就变成了这样:

\[{ f }_{ i }=max\{ { f }_{ j }+{ a({ sum }_{ i }-{ sum }_{ j }) }^{ 2 }+b{ (sum }_{ i }-{ sum }_{ j })+c\} \quad (0<j<i)\]

好了,那么斜率优化的过程是怎么实现的呢?

我们设有j<k<i且选k比选j更优.那么就有:

\[{ f }_{ j }+{ a({ sum }_{ i }-{ sum }_{ j }) }^{ 2 }+b{ (sum }_{ i }-{ sum }_{ j })+c<{ f }_{ k }+{ a({ sum }_{ i }-{ sum }_{ k }) }^{ 2 }+b{ (sum }_{ i }-{ sum }_{ k })+c\]

展开变一下形就有:

\[\cfrac { { (f }_{ j }+a{ \times { { sum }_{ j } }^{ 2 }-b\times { sum }_{ j } })-{ (f }_{ k }+a{ \times { { sum }_{ k } }^{ 2 }-b\times { sum }_{ k } }) }{ 2a\times ({ sum }_{ j }-{ sum }_{ k }) } <{ sum }_{ i }\]

这个式子长得就很像斜率的表达式:

\[\frac { { y }_{ i }-{ y }_{ j } }{ { x }_{ i }-{ x }_{ j } } \]

通过计算,还可以知道slope(i,j)如果小于slope(j,k),那么j点以及j点一下的点都可以舍去.(证明:???)(其实与f[i]相关的那个量就是直线的截距)

我们既然知道这样了,那么我们就可以使用斜率优化了.我们考虑使用单调队列处理.

我们定义一个单调队列,里面从队尾到队头斜率是单调递减的.可以理解成

\[slope(tail,tail-1)>slope(tail-1,tail-2)>...>slope(head+1,head)\]

维护单调队列中的斜率单调就可以了.我们应该可以很容易地看到从head到tail最可能成为最优解的就越靠近head.所以我们每次更新f[i]时取出head进行更新.在插入时还要考虑维护单调性,即删除那些斜率比大slope(i,tail)的点(注意:这里的tail指的是操作完成后所确定下来的tail).也就是指在队列中找到一个点恰好接上,正好保持斜率单调性就可以了.

#include <cstdio>
#include <cstring>
#include <algorithm>

typedef long long LL;

static const int maxm=1e6+10;

LL Q[maxm],A[maxm],f[maxm],sum[maxm];
LL a,b,c;
int n,head=1,tail=1;

template<class T>
T sqr(T x){
return x*x;
}

LL get_up(int i){
return f[i]+a*sqr(sum[i])-b*sum[i];
}

LL get_down(int j,int k){
return (a<<1)*(sum[j]-sum[k]);
}

double slp(int k,int j){
return (double)(get_up(j)-get_up(k))/get_down(j,k);
}

LL calc(LL x){
return a*sqr(x)+b*x+c;
}

int main(){
scanf("%d%lld%lld%lld",&n,&a,&b,&c);
for(int i=1;i<=n;i++)
scanf("%lld",&A[i]),sum[i]=sum[i-1]+A[i];

for(int i=1;i<=n;i++){
while(head<tail&&slp(Q[head+1],Q[head])<=sum[i])head++;
f[i]=f[Q[head]]+calc(sum[i]-sum[Q[head]]);
while(head<tail&&slp(i,Q[tail])<=slp(Q[tail],Q[tail-1]))tail--;
Q[++tail]=i;
}

printf("%lld\n",f
);

return 0;
}

题目传送门

贴上一个博客链接:传送门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: