标签:ref 需要 ble splay ++i lld head tail 序列
题目大意:给定序列,将它划分为\(m\)段使得方差最小,输出\(s^2*m^2\)(一个整数)。
\(\text{Solution:}\)
这题我通过题解中的大佬博客学到了一般化方差柿子的写法。
下面来推柿子:
化简得到:
两边乘以\(n^2\)得到:
其中\(sum\)是前缀和。最后这个柿子里面,\(n,sum\)都是常数,最终要处理的就是\(\sum_{i=1}^n x_i^2\).
设\(dp[i][l]\)表示前\(i\)个元素划分\(l\)次的最小平方和,有:
最终目的最小化\(dp[i][l]\)这里就是最小化\(b\),观察到\(2sum[i]\)这个斜率单调递增,所以我们维护所有大于这个斜率的决策点,做到\(O(n).\)
对于这个题,还可以滚动数组优化,虽然这里不需要。
几个实现细节:前\(i\)个元素可以划分成\(i\)段,所以每次枚举起点,它的决策起点应该是划分段数\(-1\),开始应该是划分段数对应的元素数。因为再往前往后都会导致不合法。
写\(\text{slope}\)的时候最好用\(\text{long double}\).顺序不要搞反。当然这个题主要难点是推方差柿子……\(\text{WCSL.}\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,a[20010],sum[20010];
int dp[4000][4000],tail,head;
int q[200010];
int X(int x){return sum[x];}
int Y(int x,int p){return dp[x][p-1]+sum[x]*sum[x];}
long double slope(int x,int y,int p){return (long double)(Y(y,p)-Y(x,p))/(X(y)-X(x));}
//dp[i][l]=dp[j][l-1]+(sum[i]-sum[j])^2
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;++i)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i],dp[i][1]=sum[i]*sum[i];
for(int p=2;p<=m;p++){
head=tail=1;
q[head]=p-1;
for(int i=p;i<=n;++i){
while(head<tail&&slope(q[head],q[head+1],p)<2.0*sum[i])head++;
dp[i][p]=dp[q[head]][p-1]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]]);
while(head<tail&&slope(q[tail-1],q[tail],p)>slope(q[tail-1],i,p))tail--;
q[++tail]=i;
}
}
printf("%lld\n",m*dp[n][m]-sum[n]*sum[n]);
return 0;
}
附上推柿子时\(\text{word}\)上的东西:
\(Dp[i][l]=dp[j][l-1]+(sum[i]-sum[j])^2\)
\(Dp[i]][l]=dp[j][l-1]+sum[i]^2+sum[j]^2-2sum[i]sum[j]\)
\(Dp[j][l-1]+sum[j]^2=2sum[i]sum[j]+dp[i][l]-sum[i]^2\)
\(Y=dp[j][l-1]+sum[j]^2,k=2sum[i],x=sum[j],b=dp[i][l]-sum[i]^2\)
最小化\(b\),即可
\(Ans=-sum[n]^2+m*dp[n][m]\)
标签:ref 需要 ble splay ++i lld head tail 序列
原文地址:https://www.cnblogs.com/h-lka/p/12828645.html