初学RMQ
今天学习了RMQ,参考资料是农夫三拳翻译的TC上的资料,感觉讲的挺不错
何为RMQ:RMQ-Range Minimum Query,用来查找数组中制定范围内的最小(大)元素的索引,RMQ往往和LCA是分不开的,但目前还只学习了RMQ。。
算法1:暴力枚举,O(N^3),可以通过动态规划的思想,将复杂度降到O(N^2),核心代码如下:
1 void process1(int M[MAXN][MAXN], int A[MAXN], int N) 2 3 { 4 5 int i, j; 6 7 for (i =0; i < N; i++) 8 9 M[i][i] = i; 10 11 for (i = 0; i < N; i++) 12 13 for (j = i + 1; j < N; j++) 14 15 if (A[M[i][j - 1]] < A[j]) 16 17 M[i][j] = M[i][j - 1]; 18 19 else 20 21 M[i][j] = j; 22 23 }
其中M[i][j]表示i~j内最小元素的索引,O(N^2)的复杂度还是高,难以应付大数据
算法2:将数据分段考虑,分成sqrt(N)段,预处理出每段中的最小元素的索引,可以在O(N)的时间内完成;对于查询,如图所示:
现在要查询(2,7),那么分三部分考查:A[2],M[1],A[6],A[7],分析可以得到前面一段和最后一段最多有sqrt(N)个孤立点,中间最多有sqrt(N)段,所以查询复杂度最多是3*sqrt(N),实现如下(poj3264):
1 /* 2 预处理:O(N) 查询:O(sqrt(N)) 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <cmath> 7 using namespace std; 8 9 int a[55555], _min[55555], _max[55555]; 10 int main() 11 { 12 int n, m; scanf("%d%d", &n, &m); 13 for (int i=0; i<n; i++) scanf("%d", &a[i]); 14 int l = (int)sqrt(n*1.0); 15 memset(_min, 0x1f, sizeof(_min)); 16 memset(_max, 0, sizeof(_max)); 17 _min[0] = _max[0] = 0; 18 for (int i=1; i<n; i++) 19 { 20 if (i/l == (i-1)/l) 21 { 22 if (a[i] < a[_min[i/l]]) _min[i/l] = i; 23 if (a[i] > a[_max[i/l]]) _max[i/l] = i; 24 }else{ 25 _min[i/l] = _max[i/l] = i; 26 } 27 } 28 while ( m -- ) 29 { 30 int x, y; scanf("%d%d", &x, &y); x --; y --; 31 int A = 1000001, B = 0, p = (x/l+1)*l-1, q = y/l*l; 32 if (x/l == y/l) { 33 for (int i=x; i<=y; i++) {A = min(A, a[i]); B = max(B, a[i]); } 34 printf("%d\n", B-A); continue; 35 } 36 for (int i=x; i<=p; i++) { A = min(A, a[i]); B = max(B, a[i]); } 37 for (int i=x/l+1; i<y/l; i++) { A = min(A, a[_min[i]]); B = max(B, a[_max[i]]); } 38 for (int i=q; i<=y; i++) { A = min(A, a[i]); B = max(B, a[i]); } 39 printf("%d\n", B-A); 40 } 41 return 0; 42 }
此算法可以处理较大的数据
算法3:还是分段,不过是将段的大小变为2^k,M[i][k]表示以i开始,长度为2^k区间中的最小元素索引,为了计算M[i][j]我们必须找到前半段区间和后半段区间的最小值,很明显小的片段有这2^(j-1) 长度,因此递归如下:可以得到状态转移方程:
预处理程序如下:
1 for (i = 0; i < N; i++) 2 3 M[i][0] = i; 4 5 for (j = 1; 1 << j <= N; j++) 6 7 for (i = 0; i + (1 << j) - 1 < N; i++) 8 9 if (A[M[i][j - 1]] < A[M[i + (1 << (j - 1))][j - 1]]) M[i][j] = M[i][j - 1]; 10 11 else M[i][j] = M[i + (1 << (j - 1))][j - 1];
对于查询i~j,只需要计算最大的k使得2^k<j-i+1,然后比较M[i][k]和M[j-2^k+1][k]即可,k=log(j-i+1)/log(2)
此算法的预处理为O(NlogN),查询O(1),例题poj3264:
1 /* 2 预处理:O(NlogN) 查询:O(1) 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <algorithm> 7 #include <cmath> 8 using namespace std; 9 10 int a[55555], _min[55555][50], _max[55555][50]; 11 int main() 12 { 13 int n, m; scanf("%d%d", &n, &m); 14 for (int i=0; i<n; i++) 15 { 16 _min[i][0] = _max[i][0] = i; 17 scanf("%d", &a[i]); 18 } 19 for (int j=1; 1<<j<=n; j++) 20 for (int i=0; i+(1<<j)-1<n; i++) 21 { 22 if (a[_min[i][j-1]] < a[_min[i+(1<<(j-1))][j-1]]) _min[i][j] = _min[i][j-1]; 23 else _min[i][j] = _min[i+(1<<(j-1))][j-1]; 24 if (a[_max[i][j-1]] > a[_max[i+(1<<(j-1))][j-1]]) _max[i][j] = _max[i][j-1]; 25 else _max[i][j] = _max[i+(1<<(j-1))][j-1]; 26 } 27 while ( m -- ) 28 { 29 int x, y; scanf("%d%d", &x, &y); x --; y --; 30 int k = int(log((y-x+1)*1.0)/log(2.0)+1e-6); 31 printf("%d\n", max(a[_max[x][k]], a[_max[y-(1<<k)+1][k]])-min(a[_min[x][k]], a[_min[y-(1<<k)+1][k]])); 32 } 33 return 0; 34 }
算法4:线段树,这是一个强大的工具,可以在节点中增加一个ID域,保存最小值的索引,poj3264(没有计算M数组):
1 /* 2 预处理:O(N) 查询:O(logN) 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #define lson l, m, rt<<1 7 #define rson m+1, r, rt<<1|1 8 using namespace std; 9 10 const int MAXN = 55555; 11 int _max[MAXN<<2], _min[MAXN<<2]; 12 13 void pushup(int rt) 14 { 15 _max[rt] = max(_max[rt<<1], _max[rt<<1|1]); 16 _min[rt] = min(_min[rt<<1], _min[rt<<1|1]); 17 } 18 19 void build(int l, int r, int rt) 20 { 21 if (l == r) { 22 int t; scanf("%d", &t); 23 _max[rt] = _min[rt] = t; 24 return ; 25 } 26 int m = (l+r)/2; 27 build(lson); build(rson); 28 pushup(rt); 29 } 30 31 void query(int L, int R, int l, int r, int rt, int& x, int& y) 32 { 33 if (L <= l && r <= R) { 34 x = max(x, _max[rt]); 35 y = min(y, _min[rt]); 36 return ; 37 } 38 int m = (l+r)/2; 39 if (L <= m) query(L, R, lson, x, y); 40 if (R > m) query(L, R, rson, x, y); 41 } 42 43 int main() 44 { 45 int n, m; scanf("%d%d", &n, &m); 46 build(1, n, 1); 47 while ( m -- ) 48 { 49 int a, b; scanf("%d%d", &a, &b); 50 int x = 0, y = 1000001; 51 query(a, b, 1, n, 1, x, y); 52 printf("%d\n", x-y); 53 } 54 return 0; 55 }
今天就到这了……