E
E. MEX and Increments
题目大意
给你一个数组 a a a ,数组整体求 mex \text{mex} mex ,分别求出 mex = { 0 , 1 , 2 , ⋯ , n } \text{mex} = \{0, 1, 2, \cdots, n\} mex = { 0 , 1 , 2 , ⋯ , n } 时候
需要修改几次 a a a 数组?每一次修改操作定义为,将任意位置元素 a j ← a j + 1 a_j \leftarrow a_j + 1 a j ← a j + 1
分析 mex \text{mex} mex 问题,一般是要翻译一下
不难想到对整个数组 a a a 求一下 c n t ( a i ) cnt(a_i) c n t ( a i ) ,即每个元素出现次数
mex = x \text{mex} = x mex = x ,∀ x ∈ [ 1 ⋯ n ] \forall x \in [1\cdots n] ∀ x ∈ [ 1 ⋯ n ]
ans ( x ) \text{ans}(x) ans ( x ) 有解的情况,当且仅当 [ 0 ⋯ x − 1 ] [0\cdots x-1] [ 0 ⋯ x − 1 ] 所有数都要出现,此时 c n t cnt c n t 对应的前缀和 s u m ( x − 1 ) ⩾ x sum(x-1) \geqslant x s u m ( x − 1 ) ⩾ x 才有解
进一步分析,因为只能 a j ← a j + 1 a_j \leftarrow a_j + 1 a j ← a j + 1 ,也就是说,令 y y y 是 [ 0 ⋯ x − 1 ] [0\cdots x-1] [ 0 ⋯ x − 1 ] 中空缺的数
y y y 只能由 < y < y < y 的数来补全,必然有 y < x y < x y < x
如果这些数连 mex = x \text{mex} = x mex = x 的空缺都填不上,对于 x + 1 , x + 2 , ⋯ x+1, x+2, \cdots x + 1 , x + 2 , ⋯ 更填不上了
所以如果找到一个位置 a n s ( x ) = − 1 ans(x) = -1 a n s ( x ) = − 1 ,对于 x + 1 , x + 2 , ⋯ x+1, x+2, \cdots x + 1 , x + 2 , ⋯ ,a n s ans a n s 的值均为 − 1 -1 − 1
有解的话,可以考虑 dp \text{dp} dp ,d p ( x ) dp(x) d p ( x ) 表示 { 0 , 1 , ⋯ , x − 1 } \{0, 1, \cdots, x-1\} { 0 , 1 , ⋯ , x − 1 } 全部都出现,所需要的代价
d p ( 0 ) = 0 dp(0) = 0 d p ( 0 ) = 0 ,ans ( x ) = d p ( x ) + c n t ( x ) \text{ans}(x) = dp(x) + cnt(x) ans ( x ) = d p ( x ) + c n t ( x )
即在 { 0 , 1 , ⋯ , x − 1 } \{0, 1, \cdots , x-1\} { 0 , 1 , ⋯ , x − 1 } 都出现的基础上,让 x ← x + 1 x \leftarrow x + 1 x ← x + 1 ,此时 x x x 就不会出现
然后考虑 d p ( x ) → d p ( x + 1 ) dp(x) \to dp(x+1) d p ( x ) → d p ( x + 1 ) 的转移,转移的代价就是需要补上空缺 x x x 的代价
如果 c n t ( x ) = 0 cnt(x) = 0 c n t ( x ) = 0 ,那么原序列就没有 x x x ,那么需要找到 y ∈ [ 0 ⋯ x − 1 ] y \in [0\cdots x-1] y ∈ [ 0 ⋯ x − 1 ] 这些数中,找到满足
c n t ( y ) ⩾ 2 cnt(y) \geqslant 2 c n t ( y ) ⩾ 2 的最大的那个数 y y y ,这可以边扫描边维护一个栈实现
栈中只保留 c n t ⩾ 2 cnt \geqslant 2 c n t ⩾ 2 的数,如果找不到,那么 a n s ( x + 1 , x + 2 , ⋯ n ) = − 1 ans(x+1, x+2, \cdots n) = -1 a n s ( x + 1 , x + 2 , ⋯ n ) = − 1
否则的话,d p ( x + 1 ) = d p ( x ) + ∣ x − y ∣ dp(x+1) = dp(x) + |x - y| d p ( x + 1 ) = d p ( x ) + ∣ x − y ∣ ,并且要记得更新 c n t ( x ) + + , c n t ( y ) − − cnt(x)++, cnt(y)-- c n t ( x ) + + , c n t ( y ) − −
如果 c n t ( x ) > 0 cnt(x) > 0 c n t ( x ) > 0 ,那么原序列已经有 x x x 了,不需要额外补全元素,d p ( x + 1 ) = d p ( x ) dp(x+1) = dp(x) d p ( x + 1 ) = d p ( x )
记得如果 c n t ( x ) ⩾ 2 cnt(x) \geqslant 2 c n t ( x ) ⩾ 2 的时候,将其放入栈中
一些区间有关的套路
多路归并
K 个最小和
给定 k k k 个整数数组,包含 k k k 个元素,在每一个数组中取出一个元素加起来,可以得到 k k k^k k k 种不同的元素
求这些元素中最小的 k k k 个值
先考虑下面的问题,合并两个长度为 n n n 的数组 A , B A, B A , B ,得到一个长度为 n n n 的数组 C C C
合并的过程是任意取 A , B A, B A , B 中的元素,可以得到 n 2 n^2 n 2 个和,求这些和中最小的 n n n 个,作为 C C C 数组
首先先令 A , B A, B A , B 均有序,实际上,对于有序数组,可以得到一张表
{ A 1 + B 1 ⩽ A 1 + B 2 + ⩽ ⋯ ⩽ A 1 + B n A 2 + B 1 ⩽ A 2 + B 2 + ⩽ ⋯ ⩽ A 2 + B n ⋯ A n + B 1 ⩽ A n + B 2 + ⩽ ⋯ ⩽ A n + B n \begin{cases}
A_1 + B_1 \leqslant A_1 + B_2 + \leqslant \cdots \leqslant A_1 + B_n \\
A_2 + B_1 \leqslant A_2 + B_2 + \leqslant \cdots \leqslant A_2 + B_n \\
\cdots \\
A_n + B_1 \leqslant A_n + B_2 + \leqslant \cdots \leqslant A_n + B_n
\end{cases}
⎩ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎧ A 1 + B 1 ⩽ A 1 + B 2 + ⩽ ⋯ ⩽ A 1 + B n A 2 + B 1 ⩽ A 2 + B 2 + ⩽ ⋯ ⩽ A 2 + B n ⋯ A n + B 1 ⩽ A n + B 2 + ⩽ ⋯ ⩽ A n + B n
那么最小的一个元素,必然包含在 { A 1 + B 1 , A 2 + B 1 + ⋯ A n + B 1 } \{A_1+B_1, A_2+B_1 + \cdots A_n+B_1\} { A 1 + B 1 , A 2 + B 1 + ⋯ A n + B 1 } 之中
考虑使用优先队列,维护一个二元组 ( s u m , j ) (sum, j) ( s u m , j ) ,其中 s u m = A i + B j sum = A_i + B_j s u m = A i + B j
一开始的时候,对于每一个 A i , i ∈ [ 1 , n ] A_i, \ i \in [1, n] A i , i ∈ [ 1 , n ] ,( s u m , b ) ← ( A i + B 1 , 1 ) (sum, b) \leftarrow (A_i+B_1, 1) ( s u m , b ) ← ( A i + B 1 , 1 ) 放入优先队列
然后动态地遍历 j ∈ [ 1 , n ] j \in [1, n] j ∈ [ 1 , n ] ,每一次取堆顶元素 C [ j ] ← top . s u m C[j] \leftarrow \text{top}.sum C [ j ] ← top . s u m ,b ← top . b b \leftarrow \text{top}.b b ← top . b
之后动态维护 ( s u m , b ) ⟶ ( s u m − B [ b ] + B [ b + 1 ] , b + 1 ) (sum, b) \longrightarrow (sum - B[b]+B[b+1], b+1) ( s u m , b ) ⟶ ( s u m − B [ b ] + B [ b + 1 ] , b + 1 )
k k k 路归并,实际上就是把之后的数组,合并到已经处理过的数组即可
另外需注意,假设我们检查了 ( A i + B j ) (A_i + B_j) ( A i + B j ) 是第 m m m 小,接下来要考虑 ( A i + B j + 1 ) (A_i + B_{j+1}) ( A i + B j + 1 ) 是不是第 m + 1 m+1 m + 1 小
所以要维护一个 B B B 相应的下标 b b b ,因为 B b + 1 B_{b+1} B b + 1 可能作为要检查的候选值,放入优先队列中
动态连续和问题
UVA1400
给定一个数组 D D D ,对 m m m 个问询 ( a , b ) (a, b) ( a , b ) 做出回答,需要找到 a ⩽ x ⩽ y ⩽ b a \leqslant x \leqslant y \leqslant b a ⩽ x ⩽ y ⩽ b 的 ( x , y ) (x, y) ( x , y )
使得 D x + D x + 1 + ⋯ + D y D_x + D_{x+1} + \cdots + D_{y} D x + D x + 1 + ⋯ + D y 尽量大,如果有多组满足条件,那么 x x x 尽量小,如果还有多解 y y y 应尽量小
不难想到使用线段树,具体来说,对于某个节点 p p p ,要维护的是
p r e pre p r e ,表示 p . l ⩽ p r e ⩽ p . r p.l \leqslant pre \leqslant p.r p . l ⩽ p r e ⩽ p . r ,并且 S [ p . l , p r e ] S[p.l, pre] S [ p . l , p r e ] 区间和最大
换句话说,p r e pre p r e 表示从 p . l p.l p . l 往前最远能扩展到哪里
s u f suf s u f 表示 p . r p.r p . r 往左边最远能扩展到的位置,这里扩展的意思是,使得 S [ s u f , p . r ] S[suf, p.r] S [ s u f , p . r ] 区间和最大
s u b sub s u b 用一个二元组 ( x , y ) (x, y) ( x , y ) 表示,满足 p . l ⩽ x ⩽ y ⩽ p . r p.l \leqslant x \leqslant y \leqslant p.r p . l ⩽ x ⩽ y ⩽ p . r 并且 S [ x , y ] S[x, y] S [ x , y ] 最大
维护好这些信息之后,考虑 pull \textbf{pull} pull 操作,不妨设 p p p 的两个子节点分别为 p l , p r pl, pr p l , p r
根据题目要求,定义一个 max ( s e g 1 , s e g 2 ) \max(seg1, seg2) max ( s e g 1 , s e g 2 ) 操作,如果 S ( s e g 1 ) ≠ S ( s e g 2 ) S(seg1) \neq S(seg2) S ( s e g 1 ) = S ( s e g 2 ) ,那么取区间和 S S S 较大的那一段
否则如果 s e g 1. x ≠ s e g 2. x seg1.x \neq seg2.x s e g 1 . x = s e g 2 . x ,取 x x x 较小的,x x x 仍然相同的话,取 y y y 较小的
对于 p . p r e p.pre p . p r e ,如果 S [ p l . l , p l . p r e ] ⩾ S [ p l . l , p r . p r e ] S[pl.l, pl.pre] \geqslant S[pl.l, pr.pre] S [ p l . l , p l . p r e ] ⩾ S [ p l . l , p r . p r e ] ,那么 p . p r e ← p l . p r e p.pre \leftarrow pl.pre p . p r e ← p l . p r e
否则的话,p . p r e ← p r . p r e p.pre \leftarrow pr.pre p . p r e ← p r . p r e
对于 p . s u f p.suf p . s u f ,如果 S [ p r . s u f , p r . r ] > S [ p l . s u f , p r . r ] S[pr.suf, pr.r] > S[pl.suf, pr.r] S [ p r . s u f , p r . r ] > S [ p l . s u f , p r . r ] ,(因为如果相等的话,要取尽量靠左的),p . s u f ← p r . s u f p.suf \leftarrow pr.suf p . s u f ← p r . s u f
否则的话,如果有 S [ p l . s u f , p r . r ] ⩾ S [ p r . s u f , p r . r ] S[pl.suf, pr.r] \geqslant S[pr.suf, pr.r] S [ p l . s u f , p r . r ] ⩾ S [ p r . s u f , p r . r ] ,p . s u f ← p l . s u f p.suf \leftarrow pl.suf p . s u f ← p l . s u f
接下来考虑 p . s u b p.sub p . s u b 对应的二元组如何求,实际上,p . s u b p.sub p . s u b 是以下几段的最大值
max { [ p l . s u b . x ] , [ p r . s u b ] , [ p l . s u f ⋯ p r . p r e ] } \max\{\quad [pl.sub.x], [pr.sub],[pl.suf \cdots pr.pre] \quad\} max { [ p l . s u b . x ] , [ p r . s u b ] , [ p l . s u f ⋯ p r . p r e ] }
用上面定义个 max \text{max} max 操作,求出最大的区间 [ x , y ] [x, y] [ x , y ] 即可
下面考虑问询 ( a , b ) (a, b) ( a , b )
如果找到了 ( a , b ) (a, b) ( a , b ) 对应的区间 p p p ,直接返回 p . s u b p.sub p . s u b
没找到的话,如果 b ⩽ p . mid b \leqslant p.\text{mid} b ⩽ p . mid ,只需要递归左子区间,a > p . mid a > p.\text{mid} a > p . mid ,只需要右子区间
否则同时递归左子区间和右子区间,答案有三段构成
max { [ p l . s u b ] , [ p r . s u b ] , [ p l . s u f , p r . p r e ] } \max\{ [pl.sub], [pr.sub], [pl.suf, pr.pre] \} max { [ p l . s u b ] , [ p r . s u b ] , [ p l . s u f , p r . p r e ] } ,其中 p l . s u b pl.sub p l . s u b 和 p r . s u b pr.sub p r . s u b 都可以直接通过递归来返回
递归若返回的是区间 [ x , y ] [x, y] [ x , y ] ,如何获取 s u f , p r e suf, pre s u f , p r e 等信息?
这里我们令递归返回的是 node \textbf{node} node 结点,如果同时递归左右子树,那么就新开一个结点 t m p tmp t m p
结点的 s u b , p r e , s u f sub, pre, suf s u b , p r e , s u f 信息按照如上所述更新即可,返回 t m p tmp t m p
具体来说可以复用 t m p ← pull ( node(l) , node(r) ) tmp \leftarrow \textbf{pull}(\text{node(l)}, \text{node(r)}) t m p ← pull ( node(l) , node(r) ) 函数
按照之前定义的 pull \textbf{pull} pull 规则来合并 左右子节点,并返回
滑动窗口杂题
最小覆盖子串
这是一个连续字串 的问题,很自然地想到滑动窗口
不难想到,用 cnt \textbf{cnt} cnt 这个 map \text{map} map 来维护滑动窗口中字符出现的次数
用 mp \text{mp} mp 预处理出 T T T 中字符出现的次数,那么就可以写出 check ( ) \text{check}() check ( ) 函数了
首先能想到的思路是,一开始令两个指针 i = 0 , j = 0 i = 0, j = 0 i = 0 , j = 0 ,滑动窗口为 [ i , j ] [i, j] [ i , j ]
遍历 i ∈ [ 0 , n ) i \in [0, n) i ∈ [ 0 , n ) ,对每一个 i i i ,令 j ← i j \leftarrow i j ← i
while check() = false \textbf{while} \ \text{check()} = \text{false} while check() = false ,那么就令 j ← j + 1 j\leftarrow j+1 j ← j + 1 ,同时更新 cnt \textbf{cnt} cnt
这样当 while \textbf{while} while 循环执行完之后,[ i , j ] [i, j] [ i , j ] 就是满足条件的,用来更新答案即可
下面问题来了,j j j 确定下来了,i i i 接下来要怎么走?i = i + 1 , i + 2 , ⋯ i = i+1, i+2, \cdots i = i + 1 , i + 2 , ⋯
这样对于每一个 j j j ,当 i i i 处出现冗余的 时候,[ i , i + 1 , ⋯ , j − 1 ] [i, i+1, \cdots, j-1] [ i , i + 1 , ⋯ , j − 1 ] 这么多可能都是冗余 的,这样时间复杂度是 O ( n 2 ) O(n^2) O ( n 2 ) 的
所以要换一个思路,产生上述情况的原因是
[ i , i + 1 , i + 2 , ⋯ ) [i, i+1, i+2, \cdots) [ i , i + 1 , i + 2 , ⋯ ) 一定缺少了某个元素 ,这个元素在 j j j 处才被找到
此时 [ i , i + 1 , i + 2 , ⋯ ) [i, i+1, i+2, \cdots) [ i , i + 1 , i + 2 , ⋯ ) 可能会出现冗余元素 ,基于此,可以设计出如下算法
开始时令 i = 0 , j = 0 i = 0, j = 0 i = 0 , j = 0 ,并且 i i i 先不去动它,令 j ∈ [ 0 , n ) j \in [0, n) j ∈ [ 0 , n ) 遍历
while check ( ) = 1 \textbf{while} \ \text{check}() = 1 while check ( ) = 1 ,就说明 [ i , j ] [i, j] [ i , j ] 合法,先更新答案,然后不断删除 i i i 开始的前缀冗余元素
i ← i + 1 i \leftarrow i+1 i ← i + 1 ,同时更新 cnt \textbf{cnt} cnt ,这样当 while \textbf{while} while 循环结束之后
[ i , j ] [i, j] [ i , j ] 又变成了删除冗余元素之后,不满足条件 的区间,j j j 又可以接着往前走了
这样不仅不会漏解,时间复杂度还可以保证是 O ( n ) O(n) O ( n ) 的
唯一的雪花
给定一个序列 A A A ,找一个尽量长的连续子序列 A [ l , r ] A[l, r] A [ l , r ] ,使得序列中没有相同元素
这个问题和之前的问题的区别在于,这一次是尽量长
不难想到用滑动窗口,并且用一个 set \textbf{set} set 维护滑动窗口中的元素
开始时候令 i = 0 , j = 0 i = 0, j = 0 i = 0 , j = 0 ,先不去动 i i i ,那么 j j j 能往前走的充要条件是,set ( A j ) = ∅ \textbf{set}(A_j) = \emptyset set ( A j ) = ∅
题中要求尽量长,对于起点在 i i i 位置的窗口,先不去动 i i i ,用一个 while \textbf{while} while 循环
只要 set ( A j ) = ∅ \textbf{set}(A_j) = \emptyset set ( A j ) = ∅ ,就把 A j A_j A j 放入集合中,并且令 j j j 往前走
这样 while \textbf{while} while 循环结束后,[ i , j ) [i, j) [ i , j ) 就是以 i i i 作为起点,最长的满足条件的 滑动窗口,用 j − i j-i j − i 更新答案
接下来呢? i ← i + 1 i \leftarrow i+1 i ← i + 1 往前走,同时维护 set \textbf{set} set 删除 A i A_i A i ,相当于更新窗口起点
重复以上算法即可,因为 [ i , j ) [i, j) [ i , j ) 没有冗余元素,[ i + 1 , j ) [i+1, j) [ i + 1 , j ) 也不会有冗余元素,所以 i i i 一定在 j j j 后面
扫描的时间复杂度是 O ( n ) O(n) O ( n ) ,加上 set \textbf{set} set 的维护,总时间复杂度是 O ( n log n ) O(n\log n) O ( n log n )
shuffle的播放记录
播放器中有 s s s 首歌,一开始这些歌会随机排序,全部播放完之后再随机排序,给出一个长度为 n n n 的播放记录 x i x_i x i
不一定从最开始记录,其中 1 ⩽ x i ⩽ s 1 \leqslant x_i \leqslant s 1 ⩽ x i ⩽ s ,那么请问下一次随机排序发生的位置有多少种可能?
就是问,下一次随机排序的位置,可能是 [ 1 , 2 , ⋯ , s ] [1, 2, \cdots, s] [ 1 , 2 , ⋯ , s ] ,这中间有几种是合法的呢?
这是一个确定窗口长度的滑动窗口问题,滑动窗口位置为 [ i , i + s − 1 ] [i, i+s-1] [ i , i + s − 1 ] ,不难想到如下动态维护的算法
用 t o t tot t o t 维护滑动窗口中有几个不同的元素 ,这样窗口滑动的时候,维护过程如下
if − − cnt ( A i ) = 0 \textbf{if}\ --\text{cnt}(A_i) = 0 if − − cnt ( A i ) = 0 ,t o t − − tot-- t o t − − , if + + cnt ( A i + s ) = 1 \textbf{if} \ ++\text{cnt}(A_{i+s}) = 1 if + + cnt ( A i + s ) = 1 , t o t + + tot++ t o t + + , then i + + \textbf{then}\ i++ then i + +
那么算法大致执行流程如下,枚举随机播放的位置,i ∈ [ 0 , s − 1 ] i \in [0, s-1] i ∈ [ 0 , s − 1 ] ,作为滑动窗口的起点
通过预处理判断每一个位置是否是合法起点 ,i i i 能成为答案的充要条件是
{ i , i + s , i + 2 s , ⋯ } \{i, i+s, i+2s, \cdots \} { i , i + s , i + 2 s , ⋯ } 都是滑动窗口的合法起点
预处理判断的过程也很简单,检查序列位置 [ i , i + s − 1 ] [i, i+s-1] [ i , i + s − 1 ] ,如果此时滑动窗口的 t o t = s tot = s t o t = s ,i i i 就是合法起点
算法的主过程大致如此,问题是,可能存在不完整的窗口 ,怎么办呢?可以在序列前后补 0 0 0
[ 0 ⋯ s − 1 ] ∪ [ s ⋯ s + n − 1 ] ∪ [ s + n ⋯ n + 2 s − 1 ] [0\cdots s-1] \cup [s\cdots s+n-1] \cup [s+n \cdots n+2s-1] [ 0 ⋯ s − 1 ] ∪ [ s ⋯ s + n − 1 ] ∪ [ s + n ⋯ n + 2 s − 1 ]
这样把原序列放在 [ s ⋯ s + n − 1 ] [s\cdots s+n-1] [ s ⋯ s + n − 1 ] 中,前后补 0 0 0 ,滑动窗口的起点从 0 0 0 开始,为了处理边界,加入哨兵结点
也就是说,一开始的时候滑动窗口在 [ 0 , s − 1 ] [0, s-1] [ 0 , s − 1 ] 这个位置,此时 t o t = 0 tot = 0 t o t = 0 ,滑动窗口还没有开始检查序列
此时状态也被认为是合法状态,对于不完整的段
i ⩽ s − 1 i \leqslant s-1 i ⩽ s − 1 ,如果此时 ( i + s − 1 ) − s + 1 = t o t (i+s-1)-s+1 = tot ( i + s − 1 ) − s + 1 = t o t ,那么 i i i 合法
i + s − 1 ⩾ s + n i+s-1 \geqslant s+n i + s − 1 ⩾ s + n ,如果此时 s + n − 1 − ( i ) + 1 = t o t s+n-1-(i)+1 = tot s + n − 1 − ( i ) + 1 = t o t ,那么 i i i 合法
因为加入了补 0 0 0 的段,窗口滑动的时候,注意 if A i ≠ 0 \textbf{if} \ A_i \neq 0 if A i = 0 才执行 − − cnt ( A i ) = 0 --\text{cnt}(A_i) = 0 − − cnt ( A i ) = 0 的判断
另外注意特判,s ⩾ n + 1 s \geqslant n+1 s ⩾ n + 1 ,此时播放记录中的 n n n 首歌,只是所有歌单 s s s 中的一段,不是完整的歌单
那么下一次随机播放的位置,可以是 s s s 首歌单中的任意一首,答案为 s s s
单调性优化杂题
防线
给定一个长度为 n n n 的序列,删除一个连续子序列,使得剩下的序列中有一个长度最大的,连续的,严格递增的子序列
换句话说,就是要在 A A A 中找到 [ i , j ] [i, j] [ i , j ] ,i i i 往左扩展得到最长的 连续递减 序列长度为 d p 1 ( i ) dp1(i) d p 1 ( i )
j j j 往右扩展得到最长的 连续递增 序列长度为 d p 2 ( j ) dp2(j) d p 2 ( j ) ,我们要找到 d p 1 ( i ) + d p 2 ( j ) dp1(i) + dp2(j) d p 1 ( i ) + d p 2 ( j ) 最大
首先,令 d p 1 ( i ) dp1(i) d p 1 ( i ) 表示以 i i i 结尾的最长连续递增序列 的长度
d p 2 ( i ) dp2(i) d p 2 ( i ) 表示从 i i i 开始,以 i i i 起始的最长连续递增序列 的长度,这都可以通过扫描预处理得到
对于某一个 i i i ,i ∈ [ 1 , n ] i \in [1, n] i ∈ [ 1 , n ] ,先固定 i i i 不动,对于此时的 d p 2 ( i ) dp2(i) d p 2 ( i )
要从已扫描过的序列 j ∈ [ 1 , i − 1 ] j \in [1, i-1] j ∈ [ 1 , i − 1 ] 中找到 d p 1 ( j ) dp1(j) d p 1 ( j ) 最大的,即 max j ∈ [ 1 , i − 1 ] d p 1 ( j ) \displaystyle\max_{j \in [1, i-1]} dp1(j) j ∈ [ 1 , i − 1 ] max d p 1 ( j ) ,此外还要保证 A j < A i A_j < A_i A j < A i
( max j ∈ [ 1 , i − 1 ] d p 1 ( j ) ) \left(\displaystyle\max_{j \in [1, i-1]} dp1(j)\right) ( j ∈ [ 1 , i − 1 ] max d p 1 ( j ) ) 第一反应是用单调栈维护
但很可惜这里不能单调栈,原因是我们需要的答案是 max i ( d p 2 ( i ) + max j ∈ [ 1 , i − 1 ] d p 1 ( j ) ∣ { A j < A i } ) \displaystyle\max_{i} \left(dp2(i) + \max_{j \in [1, i-1]} dp1(j) \Big| \{A_j < A_i\}\right) i max ( d p 2 ( i ) + j ∈ [ 1 , i − 1 ] max d p 1 ( j ) ∣ ∣ ∣ ∣ { A j < A i } )
如果用严格单调递增的栈来维护 max j ∈ [ 1 , i − 1 ] d p 1 ( j ) \displaystyle\max_{j \in [1, i-1]} dp1(j) j ∈ [ 1 , i − 1 ] max d p 1 ( j ) ,对于 i i i ,将 d p 1 ( i ) dp1(i) d p 1 ( i ) 入栈之前
我们需要扔掉栈中满足 x ⩾ d p 1 ( i ) x \geqslant dp1(i) x ⩾ d p 1 ( i ) 的元素,
而这些元素很可能会和 d p 2 ( i + 1 , i + 2 , ⋯ ) dp2(i+1, i+2, \cdots) d p 2 ( i + 1 , i + 2 , ⋯ ) 构成更优解,但我们把它扔掉了,就丢失了最优解
那我们不要把它扔掉不就可以了?
对于 j ∈ [ 1 , i − 1 ] j \in [1, i-1] j ∈ [ 1 , i − 1 ] ,一定是 d p 1 ( j ) dp1(j) d p 1 ( j ) 越大越好,同时 A ( j ) A(j) A ( j ) 越小越好
基于此来优化,对于 d p 1 ( j 1 ) < d p 1 ( j 2 ) dp1(j1) < dp1(j2) d p 1 ( j 1 ) < d p 1 ( j 2 ) 并且 A ( j 1 ) ⩾ A ( j 2 ) A(j1) \geqslant A(j2) A ( j 1 ) ⩾ A ( j 2 ) ,j 1 j1 j 1 是无用解
那么候选集合 j ∈ [ 1 , i − 1 ] j \in [1, i-1] j ∈ [ 1 , i − 1 ] 一定是
{ A ( j 1 ) < A ( j 2 ) < ⋯ < A ( j k ) d p 1 ( j 1 ) < d p 1 ( j 2 ) < ⋯ < d p 1 ( j k ) \begin{cases}
&A(j1) < A(j2) < \cdots < A(jk) \\
&dp1(j1) < dp1(j2) < \cdots < dp1(jk)
\end{cases}
{ A ( j 1 ) < A ( j 2 ) < ⋯ < A ( j k ) d p 1 ( j 1 ) < d p 1 ( j 2 ) < ⋯ < d p 1 ( j k )
那问题就简单了,用一个 set \textbf{set} set 维护二元组 ( A ( j ) , d p 1 ( j ) ) (A(j), dp1(j)) ( A ( j ) , d p 1 ( j ) ) ,按 A ( j ) A(j) A ( j ) 排序
对于每一个位置 i i i ,d p 1 ( j ) , j ∈ [ 1 , i − 1 ] dp1(j), j \in [1, i-1] d p 1 ( j ) , j ∈ [ 1 , i − 1 ] 最大的位置,一定是最右边的 满足 A ( j ) < A ( i ) A(j) < A(i) A ( j ) < A ( i ) 的位置
可以在 set \textbf{set} set 中二分查找到这个位置 k k k ,然后用 d p 1 ( k ) + d p 2 ( i ) dp1(k) + dp2(i) d p 1 ( k ) + d p 2 ( i ) 来更新答案
接下来考虑如何去维护? 假设当前的二元组为 c u r = ( A ( i ) , d p 1 ( i ) ) cur = (A(i), dp1(i)) c u r = ( A ( i ) , d p 1 ( i ) ) ,c u r cur c u r 按道理说应该插入 set \textbf{set} set 中 k + 1 k+1 k + 1 处
根据上面分析,如果 d p 1 ( set ( k ) ) ⩾ d p 1 ( i ) dp1(\textbf{set}(k)) \geqslant dp1(i) d p 1 ( set ( k ) ) ⩾ d p 1 ( i ) ,那么 c u r cur c u r 不应该插入 k + 1 k+1 k + 1 处
否则的话,在 set \textbf{set} set 中插入 cur = ( A ( i ) , d p 1 ( i ) ) \text{cur} = (A(i), dp1(i)) cur = ( A ( i ) , d p 1 ( i ) ) ,插入的时候注意判断一下
如果 set \textbf{set} set 中有第一关键字等于 A ( i ) A(i) A ( i ) 的元素,就把它删掉,保证候选集合中 A A A 是严格单调的
然后找到 c u r cur c u r 在 set \textbf{set} set 中的位置 p p p ,检查 p ′ = ( p + 1 , p + 2 , ⋯ ) p' = (p+1, p+2, \cdots ) p ′ = ( p + 1 , p + 2 , ⋯ )
如果 A ( set ( p ′ ) ) > A ( c u r ) A(\textbf{set}(p')) > A(cur) A ( set ( p ′ ) ) > A ( c u r ) 并且 d p 1 ( set ( p ′ ) ) ⩽ d p 1 ( c u r ) dp1(\textbf{set}(p')) \leqslant dp1(cur) d p 1 ( set ( p ′ ) ) ⩽ d p 1 ( c u r )
就在 set \textbf{set} set 中删掉 p ′ p' p ′
裁剪序列
题目大意,给定一个长度为 n n n 的序列 a a a ,将其分为若干段,满足每一段所有数的和 ⩽ M \leqslant M ⩽ M
还要满足取每一段所有数的最大值 ,然后这些最大值求和,要使得这些最大值之和最小
考虑 d p dp d p ,用 d p ( i ) dp(i) d p ( i ) 表示 [ 1 ⋯ i ] [1\cdots i] [ 1 ⋯ i ] 这些数分成若干段,满足每一段的和 ⩽ M \leqslant M ⩽ M 并且
每一段数的最大值的和 能取到的最小值,状态转移方程如下
d p ( i ) = min 1 ⩽ j < i ( d p ( j ) + ( max k ∈ [ j + 1 , i ] A k ) ∣ { ∑ k ∈ [ j + 1 , i ] A k ⩽ M } ) dp(i) = \min_{1 \leqslant j < i} \left(dp(j) + \left(\max_{k \in [j+1, i]}A_k \right)\Big| \left\{\sum_{k \in [j+1, i]} A_k \leqslant M\right\}\right)
d p ( i ) = 1 ⩽ j < i min ⎝ ⎛ d p ( j ) + ( k ∈ [ j + 1 , i ] max A k ) ∣ ∣ ∣ ∣ ⎩ ⎪ ⎨ ⎪ ⎧ k ∈ [ j + 1 , i ] ∑ A k ⩽ M ⎭ ⎪ ⎬ ⎪ ⎫ ⎠ ⎞
本例的限制条件为 ∑ k ∈ [ j + 1 , i ] A k ⩽ M \displaystyle\sum_{k \in [j+1, i]}A_k \leqslant M k ∈ [ j + 1 , i ] ∑ A k ⩽ M ,并且要取 [ j + 1 , i ] [j+1, i] [ j + 1 , i ] 区间内的最大值 max A k \max A_k max A k
那么能够成为候选值 的 A j A_j A j 一般是有单调性的
因为 max k ∈ [ j + 1 , i ] A k \max_{k \in [j+1, i]} A_k max k ∈ [ j + 1 , i ] A k 非负,所以 d p ( j ) dp(j) d p ( j ) 非严格单调递增,也就是说,如果要让 d p ( j ) dp(j) d p ( j ) 尽量小的话
j j j 应该要尽可能靠左 ,也就是说,满足 ∑ [ j , i ] ⩽ M \sum [j, i] \leqslant M ∑ [ j , i ] ⩽ M 的最左边的那个 j j j ,对应的 d p ( j − 1 ) dp(j-1) d p ( j − 1 ) 是满足条件最小的 d p dp d p 值
首先有取值 d p ( i ) ← d p ( j − 1 ) + max A [ j , i ] dp(i) \leftarrow dp(j-1) + \max A[j, i] d p ( i ) ← d p ( j − 1 ) + max A [ j , i ]
我们需要的是 min ( d p ( j ) + max A [ j + 1 , i ] ) \min\left(dp(j) + \displaystyle\max A[j+1, i]\right) min ( d p ( j ) + max A [ j + 1 , i ] ) ,这部分的值没有单调性
d p ( j ) dp(j) d p ( j ) 最小并不能够使得 d p ( j ) + A k dp(j)+ A_k d p ( j ) + A k 最小,考虑 d p dp d p 增大,A k A_k A k 减小的时候,值也会动态变化
在找到 ∑ [ j , i ] ⩽ M \sum[j, i] \leqslant M ∑ [ j , i ] ⩽ M 这个约束条件之后,还要用二叉堆维护 可能的候选值 { j , j + 1 , ⋯ , i } \{j, j+1, \cdots, i\} { j , j + 1 , ⋯ , i } ,
可能的候选值是如何变化的呢?
不难发现,∑ [ j , i ] ⩽ M \sum [j, i] \leqslant M ∑ [ j , i ] ⩽ M ,对于某一个 i i i 而言,j j j 一定越靠近 i i i 越好,同时 A j A_j A j 越大越好
j 1 < j 2 j1 < j2 j 1 < j 2 ,如果 A ( j 1 ) ⩽ A ( j 2 ) A(j1) \leqslant A(j2) A ( j 1 ) ⩽ A ( j 2 ) ,那么 j 1 j1 j 1 就是无用的解
{ j 1 < j 2 < ⋯ < j k A ( j 1 ) > A ( j 2 ) > ⋯ > A ( j k ) dp ( j ) 非严格单调增 \begin{cases}
&j1 < j2 < \cdots < jk \\
&A(j1) > A(j2) > \cdots > A(jk) \\
&\text{dp}(j) \text{非严格单调增}
\end{cases}
⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ j 1 < j 2 < ⋯ < j k A ( j 1 ) > A ( j 2 ) > ⋯ > A ( j k ) dp ( j ) 非严格单调增
基于以上分析,可以设计出如下算法,对于当前的 i i i
用单调队列维护动态变化的候选值 A j A_j A j ,A j A_j A j 的意义是,存在 [ p , i ] [p, i] [ p , i ] 区间以 A j A_j A j 作为最大值 ,p p p 应该取多少?
应该使得 d p ( p ) dp(p) d p ( p ) 越小越好,即满足约束条件的情况下,p p p 尽量靠左
不妨设左边第一个比 A j A_j A j 大的元素位置是 l e ( j ) le(j) l e ( j ) ,那么 p = le ( j ) + 1 p = \text{le}(j) + 1 p = le ( j ) + 1
实际上,单调队列维护的候选值 { j 1 , j 2 , ⋯ , j k } \{j1, j2, \cdots, jk\} { j 1 , j 2 , ⋯ , j k } ,左边第一个比 A ( j 2 ) A(j2) A ( j 2 ) 大的元素就是 A ( j 1 ) A(j1) A ( j 1 ) ,以此类推
所以 j 2 j2 j 2 在二叉堆中对应的值为 d p ( j 1 ) + max A [ j 1 + 1 ⋯ i − 1 ] = d p ( j 1 ) + A ( j 2 ) dp(j1) + \max A[j1+1 \cdots i-1] = dp(j1) + A(j2) d p ( j 1 ) + max A [ j 1 + 1 ⋯ i − 1 ] = d p ( j 1 ) + A ( j 2 )
注意 ,根据以上分析,A [ j 1 + 1 ⋯ i − 1 ] A[j1+1\cdots i-1] A [ j 1 + 1 ⋯ i − 1 ] 都是以 A ( j 2 ) A(j2) A ( j 2 ) 作为最大值的
我们要在单调队列中插入 A ( i ) A(i) A ( i ) ,首先维护队尾元素的单调性
que [ ⋯ , r 3 , r 2 , r 1 ] \text{que}[\cdots, r3, r2, r1] que [ ⋯ , r 3 , r 2 , r 1 ] ,我们有 A ( que [ r 3 ] ) > A ( que [ r 2 ] ) > A ( que [ r 1 ] ) A(\text{que}[r3]) > A(\text{que}[r2]) > A(\text{que}[r1]) A ( que [ r 3 ] ) > A ( que [ r 2 ] ) > A ( que [ r 1 ] )
如果 que [ r 1 ] ⩽ A ( i ) \text{que}[r1] \leqslant A(i) que [ r 1 ] ⩽ A ( i ) ,那么要删除 que [ r 1 ] \text{que}[r1] que [ r 1 ] ,这个元素在二叉堆中对应的值为
d p ( que [ r 2 ] ) + max A ( que [ r 2 ] + 1 , ⋯ i − 1 ) = d p ( que [ r 2 ] ) + A [ que [ r 1 ] ] dp(\text{que}[r2]) + \max A(\text{que}[r2]+1, \cdots i-1) = dp(\text{que}[r2]) + A[\text{que}[r1]] d p ( que [ r 2 ] ) + max A ( que [ r 2 ] + 1 , ⋯ i − 1 ) = d p ( que [ r 2 ] ) + A [ que [ r 1 ] ] ,也要相应地删掉
然后尝试加入 A ( i ) A(i) A ( i ) ,同时在二叉堆中加入 d p ( que [ r ] ) + max A [ que [ r ] + 1 ⋯ i ] = d p ( que [ r ] ) + A ( i ) dp(\text{que}[r]) + \max A[\text{que}[r]+1 \cdots i] = dp(\text{que}[r]) + A(i) d p ( que [ r ] ) + max A [ que [ r ] + 1 ⋯ i ] = d p ( que [ r ] ) + A ( i )
这样就完成了维护
这样我们就维护出了 d p ( j ) dp(j) d p ( j ) 动态增加,A k A_k A k 动态减少时候的最小值,取二叉堆堆顶元素即可
二叉堆维护的是动态变化 时候,可能的 A k A_k A k 值,我们还需要知道 d p ( j ) dp(j) d p ( j ) 最小可能取多少?
考虑限制条件
用一个滑动窗口,维护最左边满足 ∑ A [ p ⋯ i ] ⩽ M \sum A[p\cdots i] \leqslant M ∑ A [ p ⋯ i ] ⩽ M 的 p p p 值,此时
d p ( i ) = min ( d p ( i ) , d p ( p − 1 ) + max A [ p ⋯ i ] ) dp(i) = \min(dp(i), dp(p-1) + \max A[p\cdots i]) d p ( i ) = min ( d p ( i ) , d p ( p − 1 ) + max A [ p ⋯ i ] ) 对应的 d p ( p − 1 ) dp(p-1) d p ( p − 1 ) 是满足约束条件的最小值
另外,考虑限制,需要检查一下队首元素 que [ l 1 , l 2 , ⋯ ] \text{que}[l1, l2, \cdots] que [ l 1 , l 2 , ⋯ ]
如果 que [ l ] < p \text{que}[l] < p que [ l ] < p ,那么需要删除 que [ l ] \text{que}[l] que [ l ] ,
同时,二叉堆中对应的 d p ( que [ l 1 ] ) + max A [ que [ l 1 ] + 1 ⋯ i ] = d p ( que [ l 1 ] ) + A ( que [ l 2 ] ) dp(\text{que}[l1]) + \max A[\text{que}[l1]+1 \cdots i] = dp(\text{que}[l1]) + A(\text{que}[l2]) d p ( que [ l 1 ] ) + max A [ que [ l 1 ] + 1 ⋯ i ] = d p ( que [ l 1 ] ) + A ( que [ l 2 ] ) 取不到了
也对应地删除,另外不难发现,此时 max A [ p ⋯ i ] \max A[p\cdots i] max A [ p ⋯ i ] 就是我们维护的 que [ l ] \text{que}[l] que [ l ]
这里取 d p [ i ] ← min ( d p ( p − 1 ) + que [ l ] , 二叉堆的最小值 ) dp[i] \leftarrow \min(dp(p-1) + \text{que}[l], \ \text{二叉堆的最小值}) d p [ i ] ← min ( d p ( p − 1 ) + que [ l ] , 二叉堆的最小值 ) 就是答案