bzoj4518[Sdoi2016]征途

bzoj4518[Sdoi2016]征途

题意:

n个数,分成m段使每段和的方差尽可能小。

题解:

朴素的dp方程:f[i,m]=f[j,m-1]+(sum[i]-sum[j])2,j∈[1,i-1](sum[i]-sum[j]不用减平均数的原因是最后可以化简成f[n,m]*m-sum[n])复杂度为O(n3)会T,因为有个平方,所以考虑斜率优化,化简得到f[j,m-1]比f[k,m-1]好当且仅当((f[j,m-1]+sum[j]2)-(f[k,m-1]+sum[k]2))<2*sum[i]*(sum[j]-sum[k]),因为递推时是从小到大的顺序,所以sum[j]-sum[k]<0,所以((f[j,m-1]+sum[j]2)-(f[k,m-1]+sum[k]2))/(sum[j]-sum[k])>2*sum[i],用单调队列即可。多的那一维可以用滚动数组省空间。

代码:

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

 

20160423

posted @ 2016-07-25 14:20  YuanZiming  阅读(222)  评论(0编辑  收藏  举报