zoukankan      html  css  js  c++  java
  • RMQ区间最值查询

    RMQ区间最值查询

    概述

    RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,

    回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大值。这两个问题是在实际应用中经

    常遇到的问题,下面介绍一下解决这两种问题的比较高效的算法。

    分析:

    对于该问题,最容易想到的解决方案是遍历,复杂度是O(n)。但当数据量非常大且查询很频繁时,该算法无法

    在有效的时间内查询出正解。

    这里介绍Tarjan的Sparse-Table算法,预处理时间为O(nlogn),但查询只需要O(1),并且常数很小,算法也很容易写出。

    预处理

    设A[i]是要求区间最值的数列,F[i, j]  表示从第  i 个数起连续  2  个数中的最大值。(DP的状态)

    例如 A 数列为:3 2 4 5 6 8 1 2 9 7

    F[1,0]表示第1个数起,长度为20=1的最大值,其实就是3这个数。

    同理 F[1,1] = max(3,2) = 3, F[1,2]=max(3,2,4,5) = 5,F[1,3] = max(3,2,4,5,6,8,1,2) = 8;

    首先:F[i,0]就等于A[i]。(DP的初始值)

    其次:我们把F[i,j]平均分成两段(因为f[i,j]一定是偶数个数字),从 i 到i + 2 (j - 1) - 1为一段,i + 2  (j - 1)  到 i + 2 j - 1为一段

    于是我们得到了

    状态转移方程             F[i, j]=max(F[i,j-1], F[i + 2(j-1),j-1])

    void RMQ_init(const vector<int> &A) {
        int n = A.size();
        for(int i = 0; i < n; ++i) d[i][0] = A[i];
        for(int j = 1; (1 << j) <= n; ++j)
            for(int i = 0; i + (1 << j) - 1 < n; ++i)
                d[i][j] = min(d[i][j - 1], d[i + (1 << (j - 1))][j - 1]);
    }

     查询:

    假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂

    因为这个区间的长度为  j - i + 1,所以我们可以取   k=log2( j - i + 1) ,则有:RMQ(A, i, j)=max{   F[i , k] ,   F[ j - 2 k + 1, k]   }
    举例说明,要求区间[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2],F[8 - 2 2 + 1, 2]) = max(F[2, 2],F[5, 2]);

     int RMQ(int L, int R) {
         int k = 0;
        while((1 << (k + 1)) <= R - L + 1) ++k;
        return max(d[L][k], d[R - (1 << k) + 1][k]);
     }

    例题:

    题目描述

    老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,
    由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。
    于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,
    问题是这样的:在a到b号账中最少的一笔是多少?为了让管家没时间作假他总是一次问多个问题。

    输入格式:

    输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。
    第二行为m个数,分别是账目的钱数
    后面n行分别是n个问题,每行有2个数字说明开始结束的账目编号。

    输出格式:

    输出文件中为每个问题的答案。具体查看样例。

    输入输出样例

    输入样例#1:
    10 3
    1 2 3 4 5 6 7 8 9 10
    2 7
    3 9
    1 10
    输出样例#1:

    2 3 1

     代码:

    #include<iostream>
    #include<bits/stdc++.h>
    using namespace std;
    
    int dp[500100][20];//dp[i][j]是从第i个数起,到第2^j个数止的,最小值
    int main(){
        int n,q;
        cin>>n>>q;
        int *arr = new int[n+1];
        for (int i = 1; i <= n; i++){
            cin>>arr[i];
        }
    
        for (int i = 1; i <= n; i++)  dp[i][0]=arr[i];
        int count=0;
        while((1<<count)<=n)count++;
        // cout<<"count="<<count<<"  "<<endl;
        for(int j=1;j<=count;j++)
            for(int i=1;i<=n;i++)
                dp[i][j]=min(dp[i][j-1],dp[i+ (1<< (j-1) ) ][j-1]);
        int L,R,k;
        while (q--){
            cin>>L>>R;
            k=0;
            while (1<<(k+1)<=R-L+1)k++;
            cout<<min(dp[L][k],dp[R-(1<<k)+1][k])<<" ";
        }
    }

     

    因上求缘,果上努力~~~~ 作者:每天卷学习,转载请注明原文链接:https://www.cnblogs.com/BlairGrowing/p/14077865.html

  • 相关阅读:
    回溯算法
    再谈排序与图论算法
    Hash表
    B树和TreeSet与TreeMap
    回顾二叉树
    Spring实战第一部分总结
    Lucene6.6添加索引数据时字符个数超限,字符数不能超过BYTE_BLOCK_SIZE=32766
    第一章 机器学习基本概念
    第十至十二章 算法分析--高阶数据结构
    Lucene4.6至 Lucene6.6的每个迭代对API的改动
  • 原文地址:https://www.cnblogs.com/BlairGrowing/p/14077865.html
Copyright © 2011-2022 走看看