BZOJ4518 征途

传送门

题解:
感觉好多题解都是斜率优化…然而我这次需要用二分来解。斜率优化以后再补吧,咕
S i S_i Si为前i条路的长度和,我们先推一下:
v = ∑ i = 1 m ( x i − x ‾ ) 2 m = ∑ i = 1 m x i 2 − 2 x ‾ ∑ i = 1 m x i 2 + m x ‾ 2 m = ∑ i = 1 m x i 2 − m x ‾ 2 m v=\dfrac{\displaystyle \sum_{i=1}^m(x_i- \overline x)^2}{m}=\dfrac{\displaystyle \sum_{i=1}^{m}x_i^2-2\overline x \displaystyle \sum_{i=1}^{m}x_i^2+m\overline x^2}{m}=\dfrac{\displaystyle \sum_{i=1}^{m}x_i^2-m\overline x^2}{m} v=mi=1m(xix)2=mi=1mxi22xi=1mxi2+mx2=mi=1mxi2mx2
v × m 2 = m ∑ i = 1 m x i 2 − ( ∑ i = 1 m x i ) 2 = m ∑ i = 1 m x i 2 − S n 2 v\times m^2=m\displaystyle \sum_{i=1}^{m}x_i^2-(\displaystyle \sum_{i=1}^{m}x_i)^2=m\displaystyle \sum_{i=1}^{m}x_i^2-S_n^2 v×m2=mi=1mxi2(i=1mxi)2=mi=1mxi2Sn2
然后就相当于是把n个数分成m份,求最小的平方和。
我们可以很随便地得出一个 d p [ i ] [ j ] = min ⁡ { d p [ k ] [ j − 1 ] + ( S [ i ] − S [ k ] ) 2 }   ( 0 ≤ k &lt; i ) dp[i][j]=\min\{dp[k][j-1]+(S[i]-S[k])^2\}\ (0\le k\lt i) dp[i][j]=min{dp[k][j1]+(S[i]S[k])2} (0k<i),然后再经过一点计算之后可以得到答案。
但这玩意 O ( n 3 ) O(n^3) O(n3)的,肯定会T。
我们需要考虑优化。

除了斜率优化之外,还有一种复杂度略大的解法。
我们二分一个d,在dp过程中,每过一天就把dp值加一个d,顺便统计一下经过的最大天数。(这个鸟地方害老子wa了3次,好气哦)
然后我们可以直接这样dp: d p [ i ] = min ⁡ { d p [ j ] + d + ( S [ i ] − S [ j ] ) 2 } dp[i]=\min\{dp[j]+d+(S[i]-S[j])^2\} dp[i]=min{dp[j]+d+(S[i]S[j])2},顺便维护一下dp[i]为最小值时所需天数即可。

我是这样做的:如果经过天数 ≥ \ge m,就说明d小了,需要增大,同时计算一下ans。否则d就大了。
随便口胡一下 (保证不严谨) ,如果d比较小,那么一堆数就可能比较喜欢分开搞事,但如果d很大,一堆数就可能抱团取暖(毕竟这会子多几个d代价比多一个数平方来得更大,前面那一坨也可以这样直观感受)
可以证明(但我不想证明)一定有一个d使得可以刚好m天时最小。
注意我们每天偏移了d,要减回来。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 3005
#define LL long long
#define INF (LL)1e10
using namespace std;
int n,m,a[maxn],dys[maxn];
LL dp[maxn],sum1[maxn];
bool check(LL d)
{
	memset(dp,0x3f,sizeof(dp));
	dp[0]=dys[0]=0;
	for(int i=1;i<=n;i++)
		for(int j=0;j<i;j++)
		{
			LL sum=(sum1[i]-sum1[j])*(sum1[i]-sum1[j])+d;
			if(dp[i]>dp[j]+sum) dp[i]=dp[j]+sum,dys[i]=dys[j]+1;
			else if(dp[i]==dp[j]+sum&&dys[i]<dys[j]+1) dys[i]=dys[j]+1;
		}
	return dys[n]>=m;
}
LL BS()
{
	LL l=-INF,r=INF,ans=0;
	while(l+1<r)
	{
		LL mid=(l+r)/2;
		if(check(mid)) l=mid,ans=(dp[n]-m*mid)*m-sum1[n]*sum1[n];
		else r=mid;
	}
	return ans;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) sum1[i]=sum1[i-1]+a[i];
	printf("%lld\n",BS());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值