单调队列优化DP
新司机上路,请多指教
对于一些复杂度高的DP,形如 d p [ i ] [ j ] = m a x { s u m [ i ] + f ( k ) } dp[i][j]=max\{sum[i]+f(k)\} dp[i][j]=max{sum[i]+f(k)}(sum[i]是预处理量, f ( k ) f(k) f(k)是关于k的已知函数),可以用单调队列处理出 f ( k ) f(k) f(k)的最值,直接进行调用。
空说很难说清,看例题poj1821,题目如下:
Fence
有n个连续的篱笆,m个工人,每个工人i有一个位置Si,粉刷每个篱笆的工钱Pi,可粉刷的最大篱笆数Li(必须包括自身位置的篱笆,且粉刷的篱笆连续),假如你是工人头头,请合理分配每个工人的任务,使总工钱最大。(可以原题K这里为m)
样例
Input
n m
L1 P1 S1
L2 P2 S2
…
Lm Pm Sm
这个问题可以用递推。
为了方便根据位置排序,我们可以设一个结构体
p
[
i
]
p[i]
p[i]存三个值,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示前
i
i
i个人粉刷
j
j
j个篱笆(可以有的不粉刷)后的总工钱,发现它的递推式可以写为
d
p
[
i
]
[
j
]
=
m
a
x
{
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
k
]
+
p
[
i
]
.
p
×
(
j
−
k
)
}
dp[i][j]=max\{dp[i-1][j],dp[i-1][k]+p[i].p\times(j-k)\}
dp[i][j]=max{dp[i−1][j],dp[i−1][k]+p[i].p×(j−k)},而且k的范围为
m
a
x
{
0
,
p
[
i
]
.
s
−
p
[
i
]
.
l
+
1
,
p
[
i
−
1
]
.
s
}
<
k
<
m
i
n
{
p
[
i
]
.
s
+
p
[
i
]
.
l
,
j
}
max\{0,p[i].s-p[i].l+1,p[i-1].s\}<k<min\{p[i].s+p[i].l,j\}
max{0,p[i].s−p[i].l+1,p[i−1].s}<k<min{p[i].s+p[i].l,j}。
可以用三重循环枚举i,j,k。
改变一下递推式,可以变为
d
p
[
i
]
[
j
]
=
m
a
x
{
d
p
[
i
−
1
]
[
j
]
,
p
[
i
]
.
p
×
j
+
(
d
p
[
i
−
1
]
[
k
]
−
p
[
i
]
.
p
×
k
)
}
dp[i][j]=max\{dp[i-1][j],p[i].p\times j+(dp[i-1][k]-p[i].p\times k)\}
dp[i][j]=max{dp[i−1][j],p[i].p×j+(dp[i−1][k]−p[i].p×k)}。
对于max中逗号后的内容,
p
[
i
]
.
p
×
j
p[i].p\times j
p[i].p×j可直接求出,后面的
d
p
[
i
−
1
]
[
k
]
−
p
[
i
]
.
p
×
k
dp[i-1][k]-p[i].p\times k
dp[i−1][k]−p[i].p×k则相当于
f
(
k
)
f(k)
f(k)。因为随着
j
j
j的变大,
k
k
k的范围也随之改变,那么我们只需按
f
(
k
)
f(k)
f(k)设置
k
k
k的单调递减队列,每次调用前把不符合的
k
k
k值去除,符合的k加入,选出队列中符合的第一个
k
k
k。(每次
i
i
i改变时记得清空队列)