斜率单调暴力移指针
斜率不单调二分找答案
\(x\) 坐标单调开单调队列
\(x\) 坐标不单调开平衡树 / cdq分治
我们要求方差最小,而总和不变,等价于要每天走的路程平方和最小。
设 \(s(i)\) 表示前 \(i\) 段路的距离总和。
首先我们有一个 naive 的 \(O(n^3)\) DP:
设 \(f(i,j)\) 表示走完前 \(n\) 段,用了 \(j\) 天的最小平方和。
\[f(i,j)=\min_{k=0}^i f(k,j-1)+(s(i)-s(k))^2 \]发现后面的 \((s(i)-s(k))^2\) 明显就是斜率优化 DP,我们转化一下形式:
\[\begin{aligned} f_j(i)&=\min_{k=0}^i f_{j-1}(k)+s^2(i)+s^2(k)-2s(i)s(k) \\&=s^2(i)+\min_{k=0}^i f_{j-1}(k)+s^2(k)-2s(i)s(k) \end{aligned} \]若转移至 \(f_j(i)\) 时 \(k_0\) 不比 \(k\) 劣,则有
\[f_{j-1}(k_0)+s(k_0)^2-2s(i)s(k_0)\le f_{j-1}(k)+s^2(k)-2s(i)s(k) \]将 \(i\) 有关移到一侧:
\[\frac{(f_{j-1}(k_0)+s(k_0)^2)-(f_{j-1}(k)+s^2(k))}{s(k_0)-s(k)}\le 2s(i) \]不妨设 \(g(k)=f_{j-1}(k)+s^2(k)\),则
\[\frac{g(k_0)-g(k)}{s(k_0)-s(k)}\le 2s(i) \]发现左边就是 \((s(k_0),g(k_0)),(s(k),g(k))\) 两点间的斜率。
所以说,我们对于每一次 \(j-1\to j\),都进行斜率优化,复杂度 \(O(n^2)\)。
(\(x\) 坐标单调开单调队列)
//We'll be counting stars. //#pragma GCC optimize("Ofast") #include<bits/stdc++.h> using namespace std; #define fir first #define sec second #define mkp make_pair #define pb emplace_back #define For(i,j,k) for(int i=(j),i##_=(k);i<=i##_;i++) #define Rof(i,j,k) for(int i=(j),i##_=(k);i>=i##_;i--) #define ckmx(a,b) a=max(a,b) #define ckmn(a,b) a=min(a,b) #define debug(...) cerr<<"#"<<__LINE__<<": "<<__VA_ARGS__<<endl #define int long long #define db double #define N 3003 int n,m,a[N],f[2][N],q[N],head,tail; inline int pw(int x){ return x*x; } db SL(bool o,int x,int y){//slope return 1.*((f[o][y]+pw(a[y]))-(f[o][x]+pw(a[x])))/(a[y]-a[x]); } signed main(){ios::sync_with_stdio(false),cin.tie(nullptr); cin>>n>>m; For(i,1,n) cin>>a[i]; For(i,2,n) a[i]+=a[i-1]; For(i,1,n) f[1][i]=pw(a[i]); bool o; int tmp; For(i,2,m){ o=(i&1)^1; head=1,tail=0; For(j,1,n){ while(head<tail && SL(o,q[head],q[head+1])<2*a[j]) head++; tmp=q[head]; f[o^1][j]=f[o][tmp]+pw(a[j]-a[tmp]); while(head<tail && SL(o,q[tail-1],q[tail])>=SL(o,q[tail],j)) tail--; q[++tail]=j; } } tmp=f[m&1][n]; cout<<m*tmp-pw(a[n]); return 0;}