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