[SDOI2016]征途
[SDOI2016]征途
思路
这题目很绕(其实也还好)。
首先让我们看看要求的到底是什么。
\[ans=m^2\times s^2(s^2为方差)
\]
定义\(v\)为每一段的平均数,\(b_i\)为第\(i\)段的长度,\(sum\)为路径总长。
所以
\[s^2=\frac{1}{m}\sum_{i=1}^m(b_i-v)^2
\]
\[ans=m\sum_{i=1}^m(b_i-v)^2
\]
完全平方式展开
\[ans=m\sum_{i=1}^m(b_i^2-2b_iv+v^2)
\]
拆掉\(\sum\)
\[ans=m(mv^2-2v\sum_{i=1}^mb_i+\sum_{i=1}^mb_i^2)
\]
因为\(mv=sum且\sum_{i=1}^mb_i=sum\)所以
\[ans=m\sum_{i=1}^mb_i^2-sum^2
\]
由于\(sum和m\)都是已知的,所以我们要做的其实就是把这个序列分成\(m\)段,使得每一段的和的平方和最小。
然后可以\(DP\),设\(f[i][j]\)表示前\(i\)条路划分为\(m\)段的最优解,也就是最小平方和。
然后对路径统计前缀和,\(a[i]\)表示前\(i\)条路的总长。
显然\(f[i][j]=max\{f[k][j-1]+(a[i]-a[k])^2\}(k<i)\)
这个转移是\(O(n^3)\)的,显然是跑不出来的。
然后我们会发现,这个可以用斜率优化啊。
若决策\(s_2优于s_1(s_1<s_2)\)
当且仅当\(f[s_1][j-1]+(a[i]-a[s_1])^2>=f[s_2][j-1]+(a[i]-a[s_2])^2\)
乱搞一波化简就会得到
若有
\[a[i]>=\frac{f[s_1][j-1]+a[s_1]^2-f[s_2][j-1]-a[s_2]^2}{2(a[s_1]-a[s_2])}(s_1<s_2)
\]
那么决策\(s_2优于s_1\)
显然\(a[i]\)是单调递增的,所以我们用单调队列维护一个单调递增的斜率即可。不会的戳我
完整代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int _=3e3+20;
ll f[_][_],a[_];
int q[_];
double slope(int x,int y,int k){
return (double)((double)f[x][k]+a[x]*a[x]-f[y][k]-a[y]*a[y])/(a[x]-a[y])/2;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
a[i]+=a[i-1];
}
for(int i=1;i<=n;++i){
f[i][1]=a[i]*a[i];
}
for(int j=2;j<=m;++j){
int head=1,tail=1;
q[1]=j-1;
for(int i=j;i<=n;++i){
while(a[i]>=slope(q[head],q[head+1],j-1)&&head<tail)head++;
int k=q[head];
f[i][j]=f[k][j-1]+(a[i]-a[k])*(a[i]-a[k]);
while(slope(q[tail-1],q[tail],j-1)>=slope(q[tail],i,j-1)&&tail>head)tail--;
q[++tail]=i;
}
}
cout<<f[n][m]*m-a[n]*a[n]<<endl;
return 0;
}