zoukankan      html  css  js  c++  java
  • RMQ问题(Sparse-Table算法)

      范围最值问题(Range Minimum/maximum Query,RMQ)。给出一个哪个元素的数组A1,A2,...An,要求设计一个数据结构,支持查询操作:计算min(AL,AL+1,...,AR)或者max(AL,AL+1,...,AR)。每次都用一个循环来计算显然不够快,这里介绍Tarjan的Sparse-Table算法,它的预处理时间是O(nlogn),查询时间是O(1),因此效率更高。

      令d(i,j)表示从i开始的,长度为2j的一段元素的最小值,可以用递推的方法计算d(i, j): d(i, j) = min(d(i, j - 1), d(i + 2 j -1, j  - 1)}。

      因为长度是2j,因此d数组的大小不超过nlogn。递推代码如下:

     1 void RMQ_init(const vector<int>& A) {
     2     int n = A.size();
     3     for(int i = 0; i < n; i++) {
     4         d[i][0] = A[i];//以i开头,长度为1的最小值是A[i] 
     5     }
     6     
     7     for(int j = 1; (1 << j) <= n; j++) {//再区间范围内枚举次方 
     8         for(int i = 0; i + (1 << j) - 1 < n; i++) {//枚举每一个开头,直到没有长度为2的j的区间 
     9             d[i][j] = min(d[i][j - 1], d[i + (1 << j) - 1][j - 1]);
    10         }
    11     }
    12 } 

      查询时另k为满足2k<=R-L+1的最大整数,则以L开头、以R结尾的两个长度为2k的区间合起来也就覆盖了查询区间[L, R]。查询代码如下:

    1 int RMQ_query(int L, int R) {
    2     int k = 0;
    3     while((1 << (k + 1)) <= R - L + 1) k++;//若2的k+1次方<= R - L + 1,则k还可以加1 
    4     return min(d[L][k], d[R - (1 << k) + 1][k]);
    5 } 

      下面看一道例题:UVa 11235 Frequent values

      题意是给出一个非降序的整数数组a1,a2,a3...a4,a5,你的任务是对于一系列询问(i, j),回答在此区间中出现次数最多的值所出现的次数。

      刚看可能觉得和区间最值查询没有什么联系,我们仔细分析一下,由于数组是非降序的,可以知道数值相同的元素一定是聚在一起的,我们将整个数组进行游程编码,比如-1,1,1,2,3,3,可以写成(-1,1),(1,2),(2,1),(3,2),其中(a,b)表示有b个连续的a。我们用value[i]和count[i]分别表示第i段的数值和出现的次数,num[p],left[p],right[p],分别表示位置p所在段的编号和左右端点位置。接下来每次查询(L,R)的结果就是以下三个部分的最大值:

      从L到L所在段的结束处的元素个数(即right[L] - L + 1)

      从R所在段的开始处到R处的元素个数(即R - left[R] + 1)

      中间第num[L] + 1段到第num[R] - 1段的count的最大值(终于用到区间查询最大值,在(num[L] + 1, num[R] - 1)中的最大值)

    需要注意的特殊情况是:如果L和R在同一段中,则答案是R - L + 1。

      具体参考代码如下:

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <vector>
     4 using namespace std;
     5 
     6 const int maxn = 100000 + 10;
     7 const int maxlog = 20; 
     8  
     9 struct RMQ {
    10     int d[maxn][maxlog];//以i为开头,长度为1<<j的最值 
    11     void init(const vector<int>& A) {
    12         int n = A.size();
    13         for(int i = 0; i < n; i++)
    14             d[i][0] = A[i];//初始化 
    15         for(int j = 1; (1 << j) <= n; j++) {
    16             for(int i = 0; i + (1 << j) - 1 < n; i++) {
    17                 d[i][j] = max(d[i][j - 1], d[i + (1 << (j - 1))][j - 1]);
    18             }
    19         }
    20     }
    21     int query(int L, int R) {
    22         int k = 0;
    23         while((1 << (k + 1)) <= R - L + 1)    k++;
    24         return max(d[L][k], d[R - (1 << k) + 1][k]);
    25     }
    26 };
    27 
    28 int a[maxn], num[maxn], right[maxn], left[maxn];
    29 RMQ rmq;
    30 
    31 int main()
    32 {
    33     int n, q;
    34     while(scanf("%d%d", &n, &q) == 2 && n != 0) {
    35         for(int i = 0; i < n; i++) {
    36             scanf("%d", &a[i]);
    37         }
    38         a[n] = a[n - 1] + 1;//放置一个哨兵 
    39         
    40         int start = -1;
    41         vector<int> count;
    42         for(int i = 0; i <= n; i++) {
    43             if(i == 0 || a[i] > a[i - 1]) {//新一段的开始
    44                 if(i > 0) {
    45                     count.push_back(i - start);
    46                     for(int j = start; j < i; j++) {
    47                         num[j] = count.size() - 1;
    48                         left[j] = start;
    49                         right[j] = i - 1;
    50                     }
    51                 } 
    52                 start = i;
    53             } 
    54         }    
    55         rmq.init(count);//将每段值出现的次数作为查询的内容 
    56         
    57         int L, R, ans;
    58         while(q--) {
    59             scanf("%d%d", &L, &R);
    60             L--;R--;
    61             if(num[L] == num[R])    
    62                 ans = R - L + 1;
    63             else {
    64                 ans = max(R - left[R] + 1, right[L] - L + 1);
    65                 if(num[L] + 1 < num[R])
    66                     ans = max(ans, rmq.query(num[L] + 1, num[R] - 1));                    
    67             }            
    68             printf("%d
    ", ans);
    69         }
    70     }    
    71     return 0;
    72 }
  • 相关阅读:
    C# switch-case
    Python学习日记之中文支持
    C++学习笔记(一)之指针
    python CGI 编程实践
    linux 配置 python3 CGI
    PowerShell入门简介
    资源整合,总有你想要的
    python 爬虫之 urllib库
    一天学一个Linux命令:第一天 ls
    DG磁盘分区提示错误
  • 原文地址:https://www.cnblogs.com/wenzhixin/p/9714760.html
Copyright © 2011-2022 走看看