一个数,最小方差乘以 m^2 后的值
1≤n≤3000,保证从 S 到 T 的总路程不超过 30000
鸣谢Menci上传
题解:斜率优化
状态转移方程: f[i][j]=f[i-1][k]+(sum[j]-sum[k])^2 f[i][j]表示到第j个点,分成了i段,sum[j]表示1-j的前缀和
f[i][j]=f[i-1][k]+(sum[j]-sum[k])^2
=f[i-1][k]+sum[j]^2+sum[k]^2-2sum[j]sum[k]
=-2sum[k]sum[j]+f[i-1][k]+sum[k]^2+sum[j]^2
这个方程很显然可以用斜率优化,f[i-1][k]是上一层已经记录过的答案,我们本来需要枚举i,j,k,但是发现k一定是在j之前的,那么我们可以用单调队列维护当前层的j,计算时只需要从队列中找出使答案增加最小的j'即可,其实就相当于j,k合并成了一层循环,k就属于这一层计算过的j 。
因为要枚举i,j所以sum[j]相当于常量
把-2sum[k]当作k, sum[j]当作x,f[i-1][k]+sum[k]^2当作b
那么f[i][j]就相当于一条kx+b的直线,因为sum[k]随着k的增加而增加,而-2sum[k]随着k的增加而减小,所有我们就得到了一组斜率递减的直线。
因为x(即sum[j]是单调递增的,即X向x轴的正半轴移动),当q[head+1]的函数值小于q[head]的函数值的时候,因为斜率是单调递减的,所有q[head]对之后的答案都没有贡献了,所有就可以弹出队列。如图
当我们要加入一条直线的时候,如果是下面这幅图的情况,可以发现q[tail-1]和q[j]包含了最小值的全部,那么q[tail]就可以弹出了。(蓝色部分为最小值)
那么如何判断呢?求出交点的坐标,然后带入之后比较大小即可
q[tail-1] : k1x+b1
q[j] :k1x+b1
两个连立 k1x+b1=k1x+b1 推出 x=(b3-b1)/(k1-k3)
然后把x带入q[tail]和q[j]
k3((b3-b1)/(k1-k3))+b3<=k2((b3-b1)/(k1-k3)+b2
(k3-k2)*((b3-b1)/(k1-k3)<=b2-b3
(k3-k2)*(b3-b1)<=(b2-b3)*(k1-k3)
如果不等式成立,则q[tail]出队。
然后这道题就可以在o(n^2)的时间内完美解决了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 3003 #define ll long long #define inf 1e9 using namespace std; int n,m,q[N]; ll a[N],sum[N],f[N],g[N]; ll calc(int x,int y)//计算函数值 { return -2*sum[x]*sum[y]+g[x]+sum[x]*sum[x]+sum[y]*sum[y]; } ll s(int x) { return (ll)g[x]+sum[x]*sum[x]; } ll K(int x) { return (ll)-2*sum[x]; } ll B(int x) { return (ll)g[x]+sum[x]*sum[x]; } bool pd(int x1,int x2,int x3) { ll w1=(ll)(K(x3)-K(x2))*(B(x3)-B(x1)); ll w2=(ll)(K(x1)-K(x3))*(B(x2)-B(x3)); //cout<<w1<<" "<<w2<<endl; return w1<=w2; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) { scanf("%lld",&a[i]); sum[i]=sum[i-1]+a[i]; } ll ans=inf; for (int i=1;i<=n;i++) g[i]=inf; g[0]=0; for (int i=1;i<=m;i++) { int head=0,tail=0; for (int j=1;j<=n;j++) { while (head<tail&&calc(q[head+1],j)<=calc(q[head],j)) head++; f[j]=calc(q[head],j); while (head<tail&&pd(q[tail-1],q[tail],j)) tail--; tail++; q[tail]=j; } ans=min(ans,f[n]); for (int i=0;i<=n;i++) g[i]=f[i]; } printf("%lld\n",ans*m-sum[n]*sum[n]); }