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;
}