概念
单调队列,又称滑动窗口,常用来维护连续区间的最大值:
- 将元素入队
- 将元素出队
- 求当前队列中的最大值
实现
实现可以做到单个操作平均O(1),实际存储时队列中元素满足如下性质:
- 越先入队越靠前
- 越大越靠前
- 不符合以上两个条件的元素会被扔掉。
此时三个操作转化为:
- x入队:从队尾开始,将所有小于等于x的元素出队,然后将x入队。
- x出队:如果x已经出队,则忽略,否则将其出队。
- 最大值:队首元素即为最大值。
因为队列中实际存储的元素为单调递减,所以又称单调队列。
多重背包的单调优化
很多动态规划问题的状态转移都有类似于这样的情况:每个状态可以由上一行中的连续一段状态转移得到。
最经典的例子是多重背包:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
−
k
∗
v
o
l
]
+
k
∗
v
a
l
)
,
0
<
=
k
<
=
n
u
m
dp[i][j] = max(dp[i-1][j-k*vol]+k*val), 0<=k<=num
dp[i][j]=max(dp[i−1][j−k∗vol]+k∗val),0<=k<=num,其中
v
o
l
,
v
a
l
,
n
u
m
vol,val,num
vol,val,num分别为本种物品的体积,价值,件数。
举例来说,如果vol是1,val是2,num是3,那么 d p [ i ] [ 6 ] dp[i][6] dp[i][6]可由以下状态转移得到:
dp[i-1][3]+6
dp[i-1][4]+4
dp[i-1][5]+2
dp[i-1][6]
而 d p [ i ] [ 7 ] dp[i][7] dp[i][7]可由以下状态转移得到:
dp[i-1][4]+6
dp[i-1][5]+4
dp[i-1][6]+2
dp[i-1][7]
而 d p [ i ] [ 8 ] dp[i][8] dp[i][8]可由以下状态转移得到:
dp[i-1][5]+6
dp[i-1][6]+4
dp[i-1][7]+2
dp[i-1][8]
可以看到,三个状态的前驱状态有很大的重合部分,且满足单调队列的性质,此时可使用单调优化。
首先,为了排除后面所加的数字不同造成的影响,我们将上一列所有的状态值进行统一化处理: d p [ i − 1 ] [ j ] − = 2 ∗ j dp[i-1][j]-=2*j dp[i−1][j]−=2∗j,此时 d p [ i ] [ 6 ] dp[i][6] dp[i][6]的前驱状态是
dp[i-1][3]+12
dp[i-1][4]+12
dp[i-1][5]+12
dp[i-1][6]+12
同理,
d
p
[
i
]
[
7
]
dp[i][7]
dp[i][7]的每个前驱状态之后都为+14,此时把这些常数扔掉,在求出
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的最大值后加上
2
∗
j
2*j
2∗j即可。
此时
d
p
[
i
]
[
6
]
dp[i][6]
dp[i][6]的前驱状态是:
dp[i-1][3]
dp[i-1][4]
dp[i-1][5]
dp[i-1][6]
而 d p [ i ] [ 7 ] dp[i][7] dp[i][7]的前驱状态是:
dp[i-1][4]
dp[i-1][5]
dp[i-1][6]
dp[i-1][7]
直接使用单调队列优化即可。
价值和件数取任意值时得到的结论均相同,而当物品体积不为1时,转移就变成了当前体积除以物品体积的余数相同时,进行上述优化。
代码
因为出队操作的不确定性,单调队列需要维护两个数组:下标和值,以此判断何时应当出队。
/* 多重背包_使用单调优化 */
int main(void)
{
int T = read();
while(T--)
{
int v=read(), n=read();
int dp[M]={};
for(int i=1; i<=n; ++i)
{
int vol=read(), val=read(), num=read(); //体积,价值,数量
for(int k=0; k<vol; ++k) //枚举体积的余数
{
int a[M], b[M], l=0, r=0; //下标, 值, 队头, 队尾, 左闭右开
for(int j=k; j<=v; j+=vol)
{
int y = dp[j] - j/vol*val; //当前体积的贡献值
while(l<r && y>=b[r-1]) r--; //入队
a[r] = j; b[r++] = y;
while(a[l]<j-num*vol) ++l; //出队
dp[j] = b[l] + j/vol*val; //选择最大值
}
}
}
printf("%d\n",dp[v] );
}
return 0;
}
习题
- hdu2191 多重背包裸题,代码如上。
- hdu3401 Trade