问题描述
Pine开始了从S地到T地的征途。
从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。
Pine计划用m天到达T地。除第m天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助Pine求出最小方差是多少。
设方差是v,可以证明,\(v\times m^2\)是一个整数。为了避免精度误差,输出结果时输出\(v\times m^2\)。
输入格式
第一行两个数 n、m。
第二行 n 个数,表示 n 段路的长度
输出格式
一个数,最小方差乘以 \(m^2\) 后的值 。
样例输入
5 2
1 2 5 8 6
样例输出
36
说明
对于 \(30\%\) 的数据,\(1 \le n \le 10\)。
对于 \(60\%\) 的数据,\(1 \le n \le 100\)。
对于 \(100\%\) 的数据,\(1 \le n \le 3000\)。
保证从 S 到 T 的总路程不超过 30000 。
解析
首先,我们需要化简方差的式子,
\[
\begin{align}s^2 &=\frac{\sum_{i=1}^{m}(\overline v-v_i)^2}{m}\\ &=\frac{m\overline v^2-2\overline v(v_1+v_2+...+v_m)+(v_1^2+v_2^2+...+v_m^2)}{m}\\ &=\frac{m\frac{(\sum_{i=1}^{m}v_i)^2}{m^2}-2\frac{(\sum_{i=1}^{m}v_i)^2}{m}+v_1^2+v_2^2+...+v_m^2}{m}\\\end{align}
\]
所以
\[
s^2\times m^2=-(\sum_{i=1}^{m}v_i)^2+m(v_1^2+...+v_m^2)
\]
所以,我们需要把路程划分为m个部分,使\(v_1^2+...+v_m^2\)最小。这个可以用动态规划来完成。设\(f[i][j]\)表示将前i个数划分成j段的最小值。我们有如下状态转移方程:
\[
f[i][j]=max(f[k][j-1]+(sum[i]-sum[k])^2)
\]
然后这个转移方程可以用斜率优化。
代码
#include <iostream> #include <cstdio> #define int long long #define N 3002 using namespace std; int n,m,i,j,v[N],sum[N],f[N][N],q[N],head,tail; int read() { char c=getchar(); int w=0; while(c<'0'||c>'9') c=getchar(); while(c<='9'&&c>='0'){ w=w*10+c-'0'; c=getchar(); } return w; } double k(int x,int i,int j) { return 1.0*((f[i][x]+sum[i]*sum[i])-(f[j][x]+sum[j]*sum[j]))/(sum[i]-sum[j]); } signed main() { n=read();m=read(); for(i=1;i<=n;i++){ v[i]=read(); sum[i]=sum[i-1]+v[i]; f[i][1]=sum[i]*sum[i]; } for(j=2;j<=m;j++){ head=tail=1; q[1]=j-1; for(i=j;i<=n;i++){ while(head<tail&&k(j-1,q[head],q[head+1])<2*sum[i]) head++; int x=q[head]; f[i][j]=f[x][j-1]+(sum[i]-sum[x])*(sum[i]-sum[x]); while(head<tail&&k(j-1,q[tail],i)<k(j-1,q[tail],q[tail-1])) tail--; q[++tail]=i; } } printf("%lld\n",m*f[n][m]-sum[n]*sum[n]); return 0; }