题意
Pine开始了从S地到T地的征途。
从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。
Pine计划用m天到达T地。除第m天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助Pine求出最小方差是多少。
设方差是v,可以证明,
v
×
m
2
v×m^2
v×m2是一个整数。为了避免精度误差,输出结果时输出
v
×
m
2
v×m^2
v×m2。
题解
此题有斜率优化做法
但是这篇blog是想介绍带权二分
首先推推式子
设xi为第i天走的步数,
那么显然有
∑
i
=
1
m
x
i
=
m
x
‾
=
S
\sum_{i=1}^mx_i=m\overline x=S
∑i=1mxi=mx=S
v
=
∑
i
=
1
m
(
x
i
−
x
‾
)
2
m
=
∑
i
=
1
m
(
x
i
2
−
2
x
‾
x
i
+
x
‾
2
)
m
=
∑
i
=
1
m
x
i
2
−
∑
i
=
1
m
2
x
‾
x
i
+
∑
i
=
1
m
x
‾
2
m
=
∑
i
=
1
m
x
i
2
−
m
x
‾
2
m
v=\frac{\sum_{i=1}^m(x_i-\overline{x})^2}{m}=\frac{\sum_{i=1}^m(x_i^2-2\overline{x}x_i+\overline{x}^2)}{m}=\frac{\sum_{i=1}^mx_i^2-\sum_{i=1}^m2\overline{x}x_i+\sum_{i=1}^m\overline{x}^2}{m}=\frac{\sum_{i=1}^mx_i^2-m\overline x^2}{m}
v=m∑i=1m(xi−x)2=m∑i=1m(xi2−2xxi+x2)=m∑i=1mxi2−∑i=1m2xxi+∑i=1mx2=m∑i=1mxi2−mx2
然后
v
⋅
m
2
=
m
∑
i
=
1
m
x
i
2
−
(
m
x
‾
)
2
=
m
∑
i
=
1
m
x
i
2
−
S
2
v\cdot m^2=m\sum_{i=1}^mx_i^2-(m\overline x)^2=m\sum_{i=1}^mx_i^2-S^2
v⋅m2=mi=1∑mxi2−(mx)2=mi=1∑mxi2−S2
这个式子只与
∑
i
=
1
m
x
i
2
\sum_{i=1}^mx_i^2
∑i=1mxi2有关
那么我们就把问题转换成了:有n个数,你要把他们分成连续的m段,求最小的平方和
那么可有dp(i,j)表示前i个数,恰好分成j段
d
p
(
i
,
j
)
=
m
i
n
(
d
p
(
k
,
j
−
1
)
+
(
S
[
i
]
−
S
[
k
]
)
2
)
dp(i,j)=min(dp(k,j-1)+(S[i]-S[k])^2)
dp(i,j)=min(dp(k,j−1)+(S[i]−S[k])2)
显然会T
于是对于这种限制恰好个数的,我们就可以上今天的主角带权二分了
首先显然可以发现的一点是随着j的增大,dp(n,j)是一个下凸的曲线 (怎么证?咕咕咕…)
感性的想(理性的请转步论文),我们可以给每次增加段数的时候加上一个额外代价(或者额外收益),进而控制达到最大值时的段数
然后我们可以通过二分来调控这个额外值
最后再减去即可
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=3005;
const int INF=0x3f3f3f3f;
int x[N];
int s[N];
int n,m;
int X;
ll d[N];
int f[N];
bool check(){
memset(d,0x3f,sizeof d);
d[0]=f[0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<i;j++){
if(d[i]>d[j]+1ll*(s[i]-s[j])*(s[i]-s[j])+X){
d[i]=d[j]+1ll*(s[i]-s[j])*(s[i]-s[j])+X;
f[i]=f[j]+1;
}
else if(d[i]==d[j]+1ll*(s[i]-s[j])*(s[i]-s[j])+X&&f[i]<f[j]+1){
f[i]=f[j]+1;
}
}
return f[n]>=m;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&x[i]);
for(int i=1;i<=n;i++)
s[i]=s[i-1]+x[i];
int l=-INF,r=INF;
int ansX;
while(l<=r){
X=(l+r)>>1;
if(check()){
ansX=X;
l=X+1;
}
else{
r=X-1;
}
}
X=ansX;
check();
ll ans=(d[n]-m*X)*m-1ll*s[n]*s[n];
printf("%lld\n",ans);
}