题目链接
题意:把一个递增数列分成若干组,每组至少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]−(i−j)∗a[i])(i−j≥k)
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]−i∗a[i]+k∗a[i]≤f[j]+sum[i]−sum[j]−i∗a[i]+j∗a[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]+k∗a[i]≤f[j]−sum[j]+j∗a[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]+k∗a[i]−k∗x<=f[j]−sum[j]+j∗a[i]−j∗x。
因为
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]+k∗a[i]≤f[j]−sum[j]+j∗a[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]+k∗a[i]≤f[j]−sum[j]+j∗a[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]≤(j−k)∗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]
k−jf[k]−sum[k]−f[j]+sum[j]≤−a[i]
这就是我们要的斜率公式。
于是就可以愉快地进行斜率优化dp了!
由于离i最近的k个数不是合法决策,所以进队要有一个时间差。
出队的判断条件:
队列本质上是维护一些单调增加的斜率,也就是维护一个下凸壳。
- 当 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]要差,已经不是最优解了,所以把队头出队。
- 当
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],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\n",f[n]);
}
return 0;
}