[BZOJ4517] [SDOI2016] 征途 - 斜率优化DP

题目大意:将n个数分成m段,每段求和,求这m段和的方差的最小值*m^2

由方差公式  可进一步推导到

  否则没法优化 = =),所以我们只要求出F[i][j]代表前j个数分成i段,这i段的和的平方的最小值即可,这样这就是一个愉快的经典问题了。

显然
f(i,j)=max{f(i-1,k)+(s[j]-s[k])^2} 令ans(k)=f(i-1,k)+(s[j]-s[k])^2=f(i-1,k)+s[j]^2-2*s[j]*s[k]+s[k]^2

则有f(i-1,k)+s[k]^2=2*s[j]*s[k]+ans(k)-s[j]^2

令y=f(i-1,k)+s[k]^2,a=2*s[j],x=s[k],b=ans[k]-s[j]^2,那么
显然y=ax+b且a是定值,即斜率是定值。并且x和y单调递增,因此可以证明,如果我们将x和y看做二维上的点,所有答案的点都在当前的凸壳上,所以维护一个凸壳就好了。

#include"bits/stdc++.h"
using namespace std;
typedef long long ll;
const int N=3005;

int d[N],s[N],n,m,q[N],l,r;
ll f[N][N];

#define sqr(x) ((x)*(x))

inline double slope(int i,int k,int p)
{return (f[i-1][k]-f[i-1][p]+sqr(s[k])-sqr(s[p]))*1.0/(s[k]-s[p]);}

int main(){
	scanf("%d%d",&n,&m);int i,j;
	for(i=1;i<=n;i++)scanf("%d",&d[i]);
	for(i=1;i<=n;i++)s[i]=s[i-1]+d[i];
	memset(f,63,sizeof(f));f[0][0]=0;
	for(i=1;i<=m;i++){
		for(j=l=r=1;j<=n;j++){
			while(l<r&&slope(i,q[l],q[l+1])<s[j]*2)l++;
			if(i<=j)f[i][j]=f[i-1][q[l]]+sqr(s[j]-s[q[l]]);
			while(l<r&&slope(i,q[r-1],q[r])>slope(i,q[r],j))r--;
			q[++r]=j;
		}
	}
	printf("%lld\n",(ll)f[m][n]*m-s[n]*s[n]);
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值