缘起
【1】和【2】分别给出了RMQ问题的ST解法(本质就是打表DP)和线段树解法. 这里给出±1RMQ问题的O(N)~O(1)解法. 即算法初始化要 O(N), 而解答每次询问都只需要O(1)的时间(很快)
这种±1 RMQ 问题的应用场景是什么? 在RMQ的标准解法——O(n)转笛卡尔树, 最后转该树上的LCA问题中有用.
分析
所谓±1 RMQ问题指的是常规RMQ中涉及的数组相邻元素相差不是1就是-1.
本文以 求极大为例.
±1RMQ的想法是, 使用 L = logN/2 (这里的log是以2为底的对数)为步长分割整个数组. 则得到 N/L 个区间.
则每个区间其实都是一步+1,或者一步-1构成的序列. 其实就好像是五线谱一样. 五线谱的下一个音符比它前面一个音符高一个位或者第一个位.
注意,要明确我们想解决的问题——就是要得到任何一个区间[i,j]内部的最大值的索引,注意,我们只要索引.
我们采用分治的策略来解决±1RMQ。 将整个区间分成L的一小块一小块(每一块都是闭区间,长度为L, 不能整除就取余). 则±1RMQ问题就分治为了块内RMQ和块间RMQ.
块间RMQ是简单的——例如要求下图中i和j的最大值索引. 则只需要求出
[i,b]和[b,j]两个区间的最大者之间再取大即可(如果跨越多个块的话,其实还是一个N/L的数组的RMQ问题, 使用ST处理, 复杂度是O(N/L*log(N/L))<=O(N/L*logN)=O(N), 所以也是线性时间内可以完成的任务). 所以如果我们块内RMQ搞定了的话,则块间RMQ在O(1)时间内是可以搞定的. 而块内RMQ需要多长时间呢? 注意,这里需要用到±1RMQ的本质——相邻元素之差不是1就是-1. 这样的话,则其实一块L长度的区间高高低低的次序只有 2^L=sqrt(N) 种. 而每一种我们都可以通过暴力枚举i和j, 算出[i,j]中最大值的索引(我们RMQ问题的着眼点是求索引,所以这段高高低低的折线的起点可以忽略. 本质不同只有+1的个数和-1的个数的不一样). 这个过程需要耗费的时间是 L(枚举i)*L(枚举j)*L(求[i,j]内最大值的索引)sqrt(N)=O((logN)^3\sqrt(N))。 其实用到的事实就是一段±1高高低低的折线,不论它的起始值是多大,它的任何子区间的最大值索引是已经定下来了的. 也就是我们要的是一个相对的结果,和绝对大小无关
也就是初始化需要O(N^(1/2+alpha))时间,alpha是可以任意小的正数. 然后开始扫描整个区间. 扫描一段L长度的区间完毕之后应该就知道这段区间对应是哪种±1序列了,则O(1)时间就知道这段区间内任意子区间内的极大值的索引. 则块内RMQ问题就在O(N)+O(sqrt(N)*(logN)^3)=O(N)时间内预热完毕. 这个时间是线性的.
所以±1RMQ问题是 O(N)~O(1)的. 下面代码实现(讲真比单纯的ST要复杂很多,就为了初始化O(nlogn)降低到O(n),值得么?)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
| #include "stdafx.h" #include <stdio.h> #include <cmath> #include <algorithm> #define LOCAL
using namespace std;
const int MAX_N = 105, MAX_L = 20, MAX_SQRT_N = 20, INF = -0x3f3f3f3f;
int n,a[MAX_N],L, ans[MAX_SQRT_N][MAX_L][MAX_L], ll[MAX_L], s[MAX_N], top, st[MAX_L][MAX_N];
void dfs(int step, int x) { if (step>=L) { for (int i = 0; i< L; i++) { for (int j = i; j<L; j++) { int _max = i; for (int k = i; k<=j; k++) if (ll[k]>ll[_max]) _max = k; ans[x][i][j] = _max; } } return; } ll[step] = ll[step-1]+1; dfs(step+1, (x<<1)+1); ll[step] = ll[step-1]-1; dfs(step+1, x<<1); }
void _st() { for (int i = 0; i<top; i++) { st[0][i] = ans[s[i]][0][L-1]+i*L; } int k = 1; while(1<<k<=top) { for (int i = 0; i< top; i++) { if (i+(1<<(k-1))>=top) { st[k][i] = st[k-1][i]; } else { if (a[st[k-1][i]]>a[st[k-1][i+(1<<(k-1))]]) { st[k][i] = st[k-1][i]; } else { st[k][i] = st[k-1][i+(1<<(k-1))]; } } } k++; } }
void rmq_warmup() { while((1<<L)<=n) L++; if (L==1) L++; L>>=1; ll[0]= 0; dfs(1,0); for (int i = 0,x;i<n;i+=L) { x = 0; int j; for (j = i; j<i+L-1&&j<n-1;j++) ~(a[j+1]-a[j])?(x=(x<<1)|1):(x<<=1); x<<(i+L-1-j); s[top++] = x; } _st(); }
int rmq_ans(int i,int j) { int _max_l, _max, _max_r, _i, _j,_max_l_index, _max_r_index; _i = i/L+1; _max_l_index = i/L*L+ans[s[i/L]][i-i/L*L][L-1]; _max_l = a[_max_l_index]; _j = j/L-1; _max_r_index = j/L*L+ans[s[j/L]][0][j-j/L*L]; _max_r = a[_max_r_index]; if (_i>_j) _max = INF; else { int k = 0; while((1<<k)<=_j-_i+1) k++; _max = a[st[k-1][_i]] > a[st[k-1][_j-(1<<(k-1))+1]]?a[st[k-1][_i]]:a[st[k-1][_j-(1<<(k-1))+1]]; } return max(_max_l, max(_max, _max_r)); }
int main() { #ifdef LOCAL freopen("d:\\data.in","r",stdin); #endif scanf("%d", &n); for (int i = 0; i<n;i++) scanf("%d", a+i); rmq_warmup(); int begin,end; while(~scanf("%d%d", &begin, &end)) { if (begin>end) swap(begin,end); printf("a[%d,...,%d]内的最大值为%d\n",begin, end, rmq_ans(begin,end)); } return 0; }
|
最后形象的以下图表示±1 RMQ问题的解法(以最小RMQ为例)
参考
【1】https://yfsyfs.github.io/2019/07/15/RMQ/
【2】https://yfsyfs.github.io/2019/07/15/RMQ%E4%B9%8B%E7%BA%BF%E6%AE%B5%E6%A0%91%E8%A7%A3%E6%B3%95/