[SDOI2016]征途

题目大意:
  给你一个长度为n的数列,让你分成m段,使得每一段数字和的方差最小。
  输出最小方差*m*m。

思路:
  不难想到一个DP。
  设f[i][k]表示前i个数分成k段,则
  f[i][k]=std::min(f[i][k],m*sqr(sum[i])-2*sum[n]*sum[i]+f[j][k-1]+2*sum[n]*sum[j]-2*m*sum[i]*sum[j]+m*sqr(sum[j]));
  答案即为sqr(sum[n])+f[n][m]。
  复杂度O(n^3)。
  一开始把方程往奇怪的方向推,最后没弄出来斜率式。
  实际上如果我们用i和j表示可以被转移的两个状态,那么我们只需要维护一个关于斜率(f[i][!(k&1)]-f[j][!(k&1)]+sqr(sum[i])-sqr(sum[j]))/(sum[i]-sum[j])单调递增的队列。
  这样就可以优化到O(n^2)。

 1 #include<cstdio>
 2 #include<cctype>
 3 typedef long long int64;
 4 inline int getint() {
 5     register char ch;
 6     while(!isdigit(ch=getchar()));
 7     register int x=ch^'0';
 8     while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
 9     return x;
10 }
11 const int64 inf=0x7ffffffffffffffll;
12 const int N=3001;
13 int q[N],h,t;
14 int64 sum[N],f[N][2];
15 inline int64 sqr(const int64 &x) {
16     return x*x;
17 }
18 inline double calc(const int &i,const int &j,const int &k) {
19     return double(f[i][!(k&1)]-f[j][!(k&1)]+sqr(sum[i])-sqr(sum[j]))/double(sum[i]-sum[j]);
20 }
21 int main() {
22     const int n=getint(),m=getint();
23     for(register int i=1;i<=n;i++) sum[i]=sum[i-1]+getint();
24     for(register int i=1;i<=n;i++) f[i][0]=inf;
25     for(register int k=1;k<=m;k++) {
26         q[h=t=0]=k-1;
27         for(register int i=k;i<=n;i++) {
28             while(h<t&&calc(q[h+1],q[h],k)<=2*sum[i]) h++;
29             f[i][k&1]=f[q[h]][!(k&1)]+sqr(sum[i]-sum[q[h]]);
30             while(h<t&&calc(i,q[t],k)<=calc(q[t],q[t-1],k)) t--;
31             q[++t]=i;
32         }
33     }
34     printf("%lld\n",f[n][m&1]*m-sqr(sum[n]));
35     return 0;
36 }

 

posted @ 2017-12-22 10:25  skylee03  阅读(87)  评论(0编辑  收藏  举报