有 (n) 段路,需要分成 (m) 天走,每天走的必须是完整的若干段。希望每天走的路程的方差尽量小,求最小方差 ( imes m^2)。(n leq 3000)
Solution
设 (f[i]) 表示到某天走了 (i) 段,(g[i]) 表示到上一天走了 (i) 段,对 (g[i]) 建凸包,单调队列维护,转移到 (f[i]),转移方程为
[f[i]=min_{j<i} g[j]+s^2[i]-2s[i]s[j]+s^2[j]
]
化为斜率优化形式为
[g[j]+s^2[j]=2s[i]cdot s[j]-s^2[i]+f[i]
]
即 (y[j]=g[j]+s^2[j], x[j]=s[j], A=2s[i], B=-s^2[i])
1A了,很舒服
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3005;
int n,m,s[N],f[N],g[N],q[N],head,tail,sum;
double slope(int i,int j) {
return 1.0*(g[i]+s[i]*s[i]-g[j]-s[j]*s[j])/(s[i]-s[j]);
}
signed main() {
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>s[i], sum+=s[i];
for(int i=1;i<=n;i++) s[i]+=s[i-1];
for(int i=1;i<=n;i++) f[i]=s[i]*s[i];
for(int t=2;t<=m;t++) {
memcpy(g,f,sizeof f);
head=1; tail=0;
for(int i=1;i<=n;i++) {
while(head<tail && slope(q[head+1],q[head])<2*s[i]) ++head;
int j=q[head];
f[i]=g[j]+s[i]*s[i]-2*s[i]*s[j]+s[j]*s[j];
while(head<tail && slope(q[tail-1],q[tail])>slope(q[tail],i)) --tail;
q[++tail]=i;
}
}
cout<<m*f[n]-sum*sum;
}