这貌似是一个很神奇的新科技。
由乃救爷爷
给你一个长为 \(n\) 的序列,你需要回答 \(q\) 次询问
每次询问给出 \(l,r\),求 \(\max\limits_{i=l}^ra_i\)。
其中,\(n,m\le 2\times 10^7\)
看起来好像是一个比较模板的 RMQ,事实也确实如此。
不过一般解决 RMQ 的方法都无法在正确的时间复杂度内解决这个问题,例如线段树:预处理 \(O(n\log n)\),单次询问 \(O(\log n)\),无论预处理还是解决询问都会超时。再比如 ST 表:预处理 \(O(n\log n)\),单次询问 \(O(1)\),预处理也会超时。
这里就要讲一讲一个很强的东西了:基于整数状压的线性 RMQ。
前置:
- ST 表(必须会)
- 状态压缩(必须会)
- 分块(因为比较简单不强求)
- 单调栈(因为比较简单不强求)
算法介绍
首先,一个降低常数的办法是,先分块(假设块长为 \(B\)),记录每个块的最大值,然后把每个块当成一个元素,重新构成一个序列 \(b\),在这个序列上建立 ST 表。
对于一次询问 \(l,r\),假设 \(l\) 属于 \(p\) 块,\(r\) 属于 \(q\) 块。
-
如果 \(p\ne q\):那么 \([l,r]\) 内的最大值即为 \(b_{p+1\sim q-1}\) 的最大值与剩余两边部分的最大值,求解的时间复杂度为 \(O(\frac{n}{B}\log{\frac{n}{B}})\)。
-
如果 \(p=q\):那么直接在块内暴力查找,时间复杂度 \(O(B)\)。
此时的时间复杂度足以通过本题,但依然没有到达线性的要求,我们进行进一步强化。
假设 \(B=\log n\),对序列 \(a\) 分块后,再建立 ST 表的时间复杂度为 \(O(\frac{n}{\log n}\log{\frac{n}{\log n}})\approx O(n)\)。然后我们在记录每个块内前后缀最大值(时间复杂度 \(O(n)\))。依然假设对于一次询问 \(l,r\),\(l\) 属于 \(p\) 块,\(r\) 属于 \(q\) 块。
-
如果 \(p\ne q\):则询问可以拆成“后缀 + 大区间 + 前缀”的模式,可以 \(O(1)\) 求解。
-
如果 \(p=q\):此时就比较难绷了,好像依然需要暴力查找。
首先有一个性质,就是 \(B\) 很小,不超过 \(30\)(因为 \(B=\log n\))。
假设我们现在确定了右边界 \(r\),但是未确定左边界 \(l\)。此时有哪些候选项可能成为 \([l,r]\) 区间内的最大值?显然,候选项有多个,假设分别为 \(p_1,p_2,\dots p_k\)。思考每个 \(p_i\) 都需要满足的条件。
显然,对于任意 \(i<j\),如果 \(a_i<a_j\),则 \(a_i\) 一定不可能成为区间 \([l,r]\) 最大值。假设存在区间 \([l,r]\) 的最大值为 \(a_i\),那么显然可以用 \(a_j\) 代替 \(a_i\)。原因很简单:如果区间 \([l,r]\) 包含 \(i\),则其一定也包含 \(j\)。但是对于 \(i<j,a_i>a_j\)。\(a_j\) 依然可能成为区间最大值(可能存在 \(i<l\le j\) 的情况)。
我们维护一个单调栈,使得栈底到栈顶单调递减,每把一个数入栈,就进行一次状态压缩,为在栈中的数对应的位置为 \(1\),反之为 \(0\)。假设当前将 \(r\) 入栈,则把状态压缩后的结果储存在 \(val_r\) 中,\(val_r\) 中所有为 \(1\) 的位置即为可能成为 \([l,r]\) 最大值的候选项(\(l\le r\))。
对于查询 \([l,r]\) 中的最大值,我们取出 \(val_r\),此时小于 \(l\) 的位置的候选项均不合法,排除不合法候选项后,取出当前最靠后的一个 \(1\),即为区间 \([l,r]\) 的最大值,他满足其到 \(r\) 中的任意数都小于它,它到 \(l\) 中的任意数都不大于他。
因为要状态压缩,所有必须要求区间长度很小,而因为块长 \(B\le 30\),所以很适合对每个块进行一遍上述过程,预处理 \(O(n)\),查询 \(O(1)\)。
综上,对于任意情况,我们都可以做到趋近于线性求出区间最大值。还有一个小技巧是:将快长直接赋值 \(64\)。方便各种运算,时间复杂度也不会很劣,甚至跑的更快。
都是我口胡的,赶紧写代码。
标签:RMQ,log,询问,预处理,复杂度,最大值 From: https://www.cnblogs.com/zuoqingyuan/p/18369101