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