初学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 }
View Code

其中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 }
View Code

此算法可以处理较大的数据

算法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];
View Code

对于查询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 }
View Code

算法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 }
View Code

今天就到这了……

posted @ 2013-07-29 22:33  sxqqslf  阅读(98)  评论(0编辑  收藏  举报