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