对于:
d
p
i
=
m
i
n
/
m
a
x
(
k
j
∗
x
i
+
b
j
)
(
1
≤
i
≤
n
,
1
≤
j
≤
m
)
dp_i=min/max (k_j*x_i+b_j)(1\leq i\leq n ,1\leq j\leq m)
dpi=min/max(kj∗xi+bj)(1≤i≤n,1≤j≤m)
的dp方程可以通过"斜率优化"优化成
O
(
m
l
o
g
m
+
n
l
o
g
n
)
O(mlog_m+nlog_n)
O(mlogm+nlogn)的。
具体如下:
可以想成在一个直角坐标系中有很多直线(也就是决策),而对于
d
p
i
dp_i
dpi就是所有直线(决策)中把
x
i
x_i
xi带入算到的最优解。
如下图:
如果是
M
i
n
Min
Min的话那么最优解就构成了一个凸包的上一半(如果是
M
a
x
Max
Max就构成了一个凸包的下一半)
那怎么来构造凸包呢?怎么来查找值呢?
如果我们先考虑添加直线:
就拿
m
i
n
min
min举例吧:
如果没有直线的 k k k相等
如果一个直线的集合
S
:
L
i
n
e
1
,
L
i
n
e
2
,
L
i
n
e
3
,
L
i
n
e
4
.
.
.
.
.
.
S:Line_1,Line_2,Line_3,Line_4......
S:Line1,Line2,Line3,Line4......依次构成了这个凸包的上半部分。
通过观察上图可以发现集合中的直线
y
=
k
x
+
b
y=kx+b
y=kx+b的k是递减的。
如果设添加的这条直线是
l
l
l,集合的最后一条直线是
a
a
a。
如果 l . k < a . k l.k<a.k l.k<a.k
则通过以下方式添加:
while(S.size()>1){
if(a与倒数第二条函数交点的横坐标>=l与倒数第二条函数的横坐标){
S.pop_back();
}
}
S.push_back(l)
上面的实现是均摊
O
(
1
)
O(1)
O(1)的。
这个很容易理解,可以看下图:
可见原来a直线的取值范围内全被更优的L取代了。
如果l.k>a.k(l不是插入到最后)
那么我们需要在集合里找到最后一条直线它的k>l.k,然后,、再一个一个考虑它后面的直线,其实和上面差不多,这样是均摊 l o g n log_n logn的。
现在讲一讲查找值
如果查询的集合是{ X 1 , X 2 , X 3 . . . X m X_1,X_2,X_3...X_m X1,X2,X3...Xm}按顺序是单调的那么就可以通过"queue"一个一个的弹出:这里用递增的查询举例:
//查找x
while(q.size()>1&&q[0].query(x)>q[1].query(x)){
q.pop_front();
}
return q[0].query(x);
上面的实现是均摊 O ( 1 ) O(1) O(1)的。
如果无序的呢?
可以看下图的提示:
也就是说在这个集合里查找x对应y的最小值时,如果这个位置在
k
k
k那么
[
0
,
k
−
1
]
[0,k-1]
[0,k−1]里x对应的y的值是递增的,
[
k
+
1
,
n
]
[k+1,n]
[k+1,n]x对应的y的值也是递增的。
所以可以用binary_search找到这个位置k。