bzoj1911 [Apio2010]特别行动队

bzoj1911 [Apio2010]特别行动队

题意:

n个人,拆成若干个队。设x等于队里每个人战斗力之和,则这个队战斗力为ax2+bx+c(a,b,c已知)。求所有队战斗力总和最大多少。

题解:

方程:f[i]=max{f[j]+(sum[i]-sum[j])2*a+(sum[i]-sum[j])*b+c|1≤j<i},将方程化简可以得到只要f[j]-f[k]+a*(sum[j])2-a*(sum[k])2+b*(sum[k]-sum[j]))/(2*a*(sum[j]-sum[k])>sum[i]则说明用j递推比用k递推更优。同时这个式子是一个分式,可以看成一条线段的斜率,而斜率又满足这样一个性质:线段AB斜率<线段BC斜率<线段CD斜率,则线段AB斜率<AC斜率<AD斜率。于是我们可以用一个单调队列满足每两项的f[j]-f[k]+a*(sum[j])2-a*(sum[k])2+b*(sum[k]-sum[j]))/(2*a*(sum[j]-sum[k])单调递增,每次用队头更新(因为队头到其他点的斜率肯定大于sum[i],最优)。注意单调队列的队头是l那一边。

代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 #define inc(i,j,k) for(long long i=j;i<=k;i++)
 5 using namespace std;
 6 
 7 long long n,a,b,c,l,r,q[1000010],sum[1000010],f[1000010];
 8 inline long long sqr(long long a){return a*a;}
 9 inline double calc(long long a1,long long a2){
10     return (double)(f[a1]-f[a2]+a*sqr(sum[a1])-a*sqr(sum[a2])+b*(sum[a2]-sum[a1]))/(double)(2*a*(sum[a1]-sum[a2]));
11 }
12 int main(){
13     scanf("%lld%lld%lld%lld",&n,&a,&b,&c); 
14     sum[0]=0; inc(i,1,n){long long a1;scanf("%lld",&a1); sum[i]=sum[i-1]+a1;}
15     l=r=1;
16     inc(i,1,n){
17         while(l<r&&calc(q[l],q[l+1])<sum[i])l++;
18         long long a1=q[l]; f[i]=f[a1]+sqr(sum[i]-sum[a1])*a+(sum[i]-sum[a1])*b+c;
19         while(l<r&&calc(q[r-1],q[r])>calc(q[r],i))r--; q[++r]=i;
20     }
21     printf("%lld",f[n]);
22     return 0;
23 }

 

20160320

posted @ 2016-07-09 22:24  YuanZiming  阅读(155)  评论(0编辑  收藏  举报