分析:
简单的一维dp:
f[i]
f
[
i
]
表示在
i
i
建立一个控制站的代价,枚举上一个控制站的位置
f[i]=min(f[j]+SUM(j,i))+a[i]
f
[
i
]
=
m
i
n
(
f
[
j
]
+
S
U
M
(
j
,
i
)
)
+
a
[
i
]
其中
SUM(j,i)
S
U
M
(
j
,
i
)
表示
(j+1,i)
(
j
+
1
,
i
)
范围内的牧场到
i
i
控制站的花费
如果我们可以计算出 SUM S U M ,方程就可以在 O(n2) O ( n 2 ) 时间内转移(虽然时间还是不满足,不急,一步一步来)
假设我们在 i,j i , j 点分设两个控制站
*j j+1 j+2 *i
设 sum s u m 为 b b 的前缀和
=(sumj+1−sumj)∗(i−(j+1))+(sumj+2−sumj+1)∗(i−(j+2)) = ( s u m j + 1 − s u m j ) ∗ ( i − ( j + 1 ) ) + ( s u m j + 2 − s u m j + 1 ) ∗ ( i − ( j + 2 ) )
=sumj+1∗i−sumj+1(j+1)−sumj∗i+sumj(j+1)+sumj+2∗i−sumj+1(j+2)−sumj+1∗i+sumj+1(j+2) = s u m j + 1 ∗ i − s u m j + 1 ( j + 1 ) − s u m j ∗ i + s u m j ( j + 1 ) + s u m j + 2 ∗ i − s u m j + 1 ( j + 2 ) − s u m j + 1 ∗ i + s u m j + 1 ( j + 2 )
=i∗(sumj+2−sumj)−(sumj+1(j+1)+sumj+2(j+2))+sumj(j+1)+sumj+1(j+2) = i ∗ ( s u m j + 2 − s u m j ) − ( s u m j + 1 ( j + 1 ) + s u m j + 2 ( j + 2 ) ) + s u m j ( j + 1 ) + s u m j + 1 ( j + 2 )
我们预处理一下 ∑sumi∗i,∑sumi(i+1) ∑ s u m i ∗ i , ∑ s u m i ( i + 1 ) 即可
下面就是斜率形式的转化:
假设
k
k
点作为转移点比点优秀,则有
设(
ds[i]=∑ij=1sumj∗j,dss[i]=∑ij=1sumj−1∗j
d
s
[
i
]
=
∑
j
=
1
i
s
u
m
j
∗
j
,
d
s
s
[
i
]
=
∑
j
=
1
i
s
u
m
j
−
1
∗
j
)
设 get(j,k)=f[j]+ds[j]−dss[j]−(f[k]+ds[k]−dss[k])sum[j]−sum[k] g e t ( j , k ) = f [ j ] + d s [ j ] − d s s [ j ] − ( f [ k ] + d s [ k ] − d s s [ k ] ) s u m [ j ] − s u m [ k ]
斜率优化做法如下:
- 用单调队列维护上凸性
- 转移:
假设队列从头到尾有元素 a,b,c,... a , b , c , . . .
若 get(a,b)<i g e t ( a , b ) < i ,说明 b b 点作为转移点更优秀,出队
直到 get(x,y)>=i g e t ( x , y ) >= i , f[i]=f[x]+SUM(x,i) f [ i ] = f [ x ] + S U M ( x , i ) - 入队:
假设队列尾部依次有元素 ...,a,b,c . . . , a , b , c ,若要加入 d d 元素
若,说明 c c 点作为转移点不够优秀,出队
直到 get(x,d)>=get(x,y) g e t ( x , d ) >= g e t ( x , y ) ,插入 d d
tip
感觉斜率优化dp都有一种深深的套路感:
- 序列上的dp
- 花费一般是在一个区间上
- 可以很simple的想出的转移方法
- 式子可以化出斜率的形式
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N=1000005;
int n,Q[N],tou=0,wei=0;
ll sum[N],a[N],ds[N],dss[N],f[N];
ll SUM(int x,int y) {
return y*(sum[y-1]-sum[x])-(ds[y-1]-ds[x])+(dss[y-1]-dss[x]);
}
double get(int j,int k) {
return (double)(f[j]+ds[j]-dss[j]-(f[k]+ds[k]-dss[k]))/(double)(sum[j]-sum[k]);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for (int i=1;i<=n;i++)
scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
for (int i=1;i<=n;i++) {
ds[i]=ds[i-1]+sum[i]*(ll)i;
dss[i]=dss[i-1]+sum[i-1]*(ll)(i);
}
for (int i=1;i<=n;i++) {
while (tou<wei&&get(Q[tou],Q[tou+1])<i) tou++;
f[i]=f[Q[tou]]+SUM(Q[tou],i)+a[i];
while (tou<wei&&get(Q[wei],i)<get(Q[wei-1],Q[wei])) wei--;
Q[++wei]=i;
}
printf("%lld\n",f[n]);
return 0;
}