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

[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[n]);
	
	return 0;
}

题目传送门
贴上一个博客链接:传送门

posted @ 2017-05-20 15:48  Exbilar  阅读(187)  评论(0编辑  收藏  举报