【poj3709】K-Anonymous Sequence 【斜率优化dp】

题目链接
题意:把一个递增数列分成若干组,每组至少k个,每组的花费是这组的数字和减去最小值乘这组的总个数。求最小总花费。
首先,我们想一个朴素的dp方程。把这个序列翻转过来, f[i]表示前i个数的最小花费,方程为:
f [ i ] = m i n ( f [ j ] + s u m [ i ] − s u m [ j ] − ( i − j ) ∗ a [ i ] ) ( i − j ≥ k ) f[i]=min(f[j]+sum[i]-sum[j]-(i-j)*a[i])(i-j≥k) f[i]=min(f[j]+sum[i]sum[j](ij)a[i])(ijk)
sum是前缀和,a代表每个数字。
朴素的时间复杂度是O(n^2)的。我们发现可以对其使用斜率优化。
决策单调性证明:
设现在要求i的答案,并且k是j后面的一个决策(即k>j),且k的答案不比j差。
把dp方程展开后得
f [ k ] + s u m [ i ] − s u m [ k ] − i ∗ a [ i ] + k ∗ a [ i ] ≤ f [ j ] + s u m [ i ] − s u m [ j ] − i ∗ a [ i ] + j ∗ a [ i ] f[k]+sum[i]-sum[k]-i*a[i]+k*a[i]≤f[j]+sum[i]-sum[j]-i*a[i]+j*a[i] f[k]+sum[i]sum[k]ia[i]+ka[i]f[j]+sum[i]sum[j]ia[i]+ja[i]
整理得
f [ k ] − s u m [ k ] + k ∗ a [ i ] ≤ f [ j ] − s u m [ j ] + j ∗ a [ i ] f[k]-sum[k]+k*a[i]≤f[j]-sum[j]+j*a[i] f[k]sum[k]+ka[i]f[j]sum[j]+ja[i]
设i后面有一个决策l, x = a [ l ] − a [ i ] x=a[l]-a[i] x=a[l]a[i]
则我们要证明 f [ k ] − s u m [ k ] + k ∗ a [ i ] − k ∗ x < = f [ j ] − s u m [ j ] + j ∗ a [ i ] − j ∗ x f[k]-sum[k]+k*a[i]-k*x<=f[j]-sum[j]+j*a[i]-j*x f[k]sum[k]+ka[i]kx<=f[j]sum[j]+ja[i]jx
因为 f [ k ] − s u m [ k ] + k ∗ a [ i ] ≤ f [ j ] − s u m [ j ] + j ∗ a [ i ] f[k]-sum[k]+k*a[i]≤f[j]-sum[j]+j*a[i] f[k]sum[k]+ka[i]f[j]sum[j]+ja[i],且 k > j k>j k>j,x相同,
所以上面那个成立,这个dp方程具有决策单调性。
因此对于任何k>j且k对i的答案不差于j对i的答案的两个决策,k对任何l>i的答案都不差于j对l的答案。
斜率优化:
刚才的式子: f [ k ] − s u m [ k ] + k ∗ a [ i ] ≤ f [ j ] − s u m [ j ] + j ∗ a [ i ] f[k]-sum[k]+k*a[i]≤f[j]-sum[j]+j*a[i] f[k]sum[k]+ka[i]f[j]sum[j]+ja[i]
移项得: f [ k ] − s u m [ k ] − f [ j ] + s u m [ j ] ≤ ( j − k ) ∗ a [ i ] f[k]-sum[k]-f[j]+sum[j]≤(j-k)*a[i] f[k]sum[k]f[j]+sum[j](jk)a[i]
由于k < j,所以j-k是负数,除过来时要注意变号。
把两边同时除以(k-j)得
f [ k ] − s u m [ k ] − f [ j ] + s u m [ j ] k − j ≤ − a [ i ] \frac{f[k]-sum[k]-f[j]+sum[j]}{k-j}≤-a[i] kjf[k]sum[k]f[j]+sum[j]a[i]
这就是我们要的斜率公式。
于是就可以愉快地进行斜率优化dp了!
由于离i最近的k个数不是合法决策,所以进队要有一个时间差。
出队的判断条件:
队列本质上是维护一些单调增加的斜率,也就是维护一个下凸壳。

  1. s l o p e ( q [ h e a d ] , q [ h e a d + 1 ] ) < = − a [ i ] slope(q[head],q[head+1])<=-a[i] slope(q[head],q[head+1])<=a[i]时,q[head]比q[head+1]要差,已经不是最优解了,所以把队头出队。
  2. s l o p e ( q [ t a i l ] , i − m + 1 ) < s l o p e ( q [ t a i l − 1 ] , q [ t a i l ] ) slope(q[tail],i-m+1)<slope(q[tail-1],q[tail]) slope(q[tail],im+1)<slope(q[tail1],q[tail])时,i-m+1比q[tail]要优,所以就把q[tail]出队了。
    细节详见代码。
#include<cstdio>
#define int long long
const int N=500005;
int kase,n,m,head,tail,a[N],sum[N],f[N],q[N];
double slope(int j,int k){
	return (1.0*f[k]-sum[k]-f[j]+sum[j])/(k-j);
}
signed main(){
	scanf("%lld",&kase);
	while(kase--){
		scanf("%lld%lld",&n,&m);
		for(int i=1;i<=n;i++){
			scanf("%lld",&a[n-i+1]);
		}
		for(int i=1;i<=n;i++){
			sum[i]=sum[i-1]+a[i];
		}
		for(int i=m;i<2*m&&i<=n;i++){
			f[i]=sum[i]-i*a[i];
		}
		head=1,tail=0;
		q[++tail]=m;
		for(int i=2*m;i<=n;i++){
			while(head<tail&&slope(q[head],q[head+1])<=-a[i]){
				head++;
			}
			f[i]=f[q[head]]+sum[i]-sum[q[head]]-(i-q[head])*a[i];
			while(head<tail&&slope(q[tail],i-m+1)<slope(q[tail-1],q[tail])){
				tail--;
			}
			q[++tail]=i-m+1;
		}
		printf("%lld\n",f[n]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值