洛谷题目传送门
一开始肯定要把题目要求的式子给写出来
我们知道方差的公式\(s^2=\frac{\sum\limits_{i=1}^{m}(x_i-\overline x)^2}{m}\)
题目要乘\(m^2\)再输出,于是
\(m^2s^2=m\sum\limits_{i=1}^{m}(x_i-\overline x)^2\)
\(=m(\sum\limits_{i=1}^{m}x_i^2-2\overline{x}\sum\limits_{i=1}^{m}x_i+m\overline{x}^2)\)
\(=m\sum\limits_{i=1}^{m}x_i^2-(\sum\limits_{i=1}^{m}x_i)^2\)
于是只要最小化\(\sum\limits_{i=1}^{m}x_i^2\)即可。
然而选\(m\)段非常不好办。这时候可以联想到凸优化。设\(G_m\)表示选\(m\)段\(\sum\limits_{i=1}^{m}x_i^2\)的最小值,当\(m\)增大的时候\(G_m\)显然会减小,凭蒟蒻的感性理解,多分出一段对答案的影响幅度也越来越小,也就是说\(G_x\)关于\(x\)的函数图像大概是下凸的。
我们用一个斜率为\(mid\)的直线去切这个凸包。显然\(mid\)的下界取\([0,1]\)之间的斜率,是总路程平方级别的,上界是\(0\)。因为切线在凸包的下方,所以多选一段的代价不是\(+mid\)而是\(-mid\)。
update:蒟蒻弃用了用直线切凸包的理解方法,蒟蒻用导数思想理解DP凸优化的思路可以看这里
接下来就是斜率优化的过程。设\(f_i\)为前\(i\)条路的最优答案,\(x_i\)为路程长度的前缀和,写出转移方程
\(f_i=\min\limits_{j=0}^{i}\{f_j-2x_ix_j+x_j^2\}+x_i^2\)
决策\(j\)优于\(k\)当且仅当
\(f_j-2x_ix_j+x_j^2
\(\frac{f_j+x_j^2-f_k-x_k^2}{x_j-x_k}<2x_i\)
于是设\(y_i=f_i+x_i^2\),把决策看成点\((x_i,y_i)\),使用单调队列就OK了。注意这里要记\(c_i\)表示最优决策下将前\(i\)条路分出的段数。最后判断\(c_n\)与\(m\)的关系来调整斜率。
由于这一题的斜率肯定不会有小数,故也不必担心二分中的一些边界问题。
#include
#define RG register
#define R RG int
#define G c=getchar()
#define Calc(j,k) (y[j]-y[k])/(x[j]-x[k])
typedef long long LL;
const int N=3009;
int n,q[N],c[N];
double f[N],k[N],x[N],y[N];
inline int in(){
RG char G;
while(c<'-')G;
R x=c&15;G;
while(c>'-')x=x*10+(c&15),G;
return x;
}
inline double sqr(RG double x){
return x*x;
}
inline void work(R mid){//斜率优化
R h,t,i;
for(h=t=i=1;i<=n;++i){
while(hCalc(q[t],i))--t;
k[t]=Calc(q[t],i);q[++t]=i;
}
}
int main(){
n=in();R m=in(),l,r,mid,i;
for(i=1;i<=n;++i)x[i]=x[i-1]+in();
l=-sqr(x[n]);r=0;//大致确定下界
while(l