P4072-[SDOI2016] 征途

P4072 [SDOI2016] 征途

Description

把一些数据分组,使得每组方差和最小。

Solution

设方差和为 \(D_x\)

\(m^2\times D_X=m\sum_{i\le m}x_i^2-(\sum_{i\le m}x_i^2)\),第二项没有影响,我们考虑对第一项求 \(\min\)

\(f_{i}\) 表示前 \(i\) 个数的平方和最小值。我们有 \(f_i=\min(f_k+(s_i-s_k)^2)\)。(\(k\leq i\)

我们考虑斜率优化 dp

拆开式子有 \(f_i=\min(f_k+s_i^2+s_k^2-2s_is_k)\)\(s_i\) 为定值,暂不考虑。

则有 \(f_i= f_k+s_k^2-2s_ks_i\),我们考虑设 \(A_k\)\(f_k+s_k^2\),设 \(B_k\)\(-2s_k\),设 \(C_i\)\(s_i\)

由于 \(B_k,C_i\) 均单调,则我们考虑用双端队列维护答案和斜率。

我们考虑开一个双端队列,每次对于一个新的 \(i\),先查询一个答案,对于当前状态,答案指针向前移动,找到一个当前 \(C_i\) 最先与凸包相切的点,而后我们考虑将这个答案插入凸包内,并持续判断队尾是否不优,如果不优,那么持续弹出,从而保证凸包内斜率的单调性。

由于每个点最多只能被作为答案或者删除一次,所以这个复杂度是 \(O(n)\)的。

复杂度 \(O(nm)\)

Code

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+7;

int n,m;
int sum[N];
int qu[N];
int f[N],g[N];

double X(int i){
  return -2*sum[i];
}

double Y(int i){
  return g[i]+sum[i]*sum[i];
}

double check(int x,int y){
  return ((Y(x)-Y(y))/(X(x)-X(y)));
}

int main(){
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++){
    int temp;scanf("%d",&temp);sum[i]=sum[i-1]+temp;
    g[i]=sum[i]*sum[i];
  }
  for(int k=1;k<m;k++){
    int hd=1,tl=1;
    qu[1]=k;
    for(int i=k+1;i<=n;i++){
      while(hd<tl&&check(qu[hd],qu[hd+1])> -sum[i]) ++hd;
      int ans=qu[hd];
      f[i]=g[ans]+(sum[i]-sum[ans])*(sum[i]-sum[ans]);
      while(hd<tl&&check(qu[tl],qu[tl-1])<check(qu[tl],i)) --tl;
      qu[++tl]=i;
      // printf("%d %d %d %lf\n",ans,i,f[i],check(2,3));
    }
    for(int i=1;i<=n;i++) g[i]=f[i]; 
  }
  printf("%d",-sum[n]*sum[n]+m*f[n]);
  return 0;
}
posted @ 2023-08-14 16:28  Zimo_666  阅读(12)  评论(1编辑  收藏  举报