• 动态规划——稀疏表求解RMQ问题


    RMQ (Range Minimum/Maximum Query)问题,即区间最值查询问题,是求解序列中的某一段的最值的问题。如果只需要询问一次,那遍历枚举(复杂度O(n))就是最方便且高效的方法,但如果询问次数很多(m次),O(nm)的复杂度可能就不够看了。比较容易想到的优化方法是运用预处理的思想,可以在O(n^2)的时间内预处理出所有区间的最大值,随后每一次查询都只需要O(1)的时间。这种方法在n较小但m非常大的情况下很实用,但如果n也很大的话,无论是空间还是时间都接受不了这个复杂度。

    这种情况下,我们可以借助稀疏表(sparse table)和动态规划的思想,避免笨重的逐个预处理的方法:

    设dp[i][j]代表从第i个元素开始,长度为2^j的区间中的最值。那么dp[i][0]就等于原序列中的第i个元素,dp[i][j]则可以由两段长度为2^(j-1)的区间合并而成。这样,就可以在o(nlogn)的时间内完成预处理。在查询时,任一区间都可以被已经预处理出来的两个区间恰好覆盖,找出这两个区间即可完成查询。预处理的时间和空间复杂度都为O(nlogn),查询为O(1)。

    下面以最大值为例,附上实现代码和需要注意的细节。

    1、预处理

    int n;
    int a[maxn], dp[maxn][maxj];//a为原序列,元素编号为1至n
    void init()//预处理
    {
        for(int i = 1;i <= n;i++)//初始化
        {
            for(int j = 1;j < maxj;j++)
            {
                dp[i][j] = -INF;//这里可根据需要赋成其他值或是省略,此处为无穷小
            }
            dp[i][0] = a[i];//dp的初始值
        }
        //因为状态转移是把两段较短的区间合并,所以要先处理出短的区间,j应在循环的外层
        for(int j = 1;(1 << j) <= n;j++)//1 << j 运用了位运算,代表2^j
        {
            for(int i = 1;(1 << j) + i - 1 <= n;i++)//dp[i][j]覆盖的区间为[i, (1 << j) + i - 1]
            {
                dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);//状态转移方程,书写这里时要注意位运算符的优先级
            }
        }
    }

    2、查询

    为了保证查询的准确性,待查区间一定要被预处理出来的区间恰好覆盖。实现这一点,只需要两段长度小于待查区间的且长度为2的幂的区间即可。例如区间[1, 5]可以用[1, 4]和[2, 5]来覆盖。

    如果待查区间长度为len的话,我们只需要查询长度为2^的两段区间就可以了(注意log得到的结果这里向下取整了)。

    int ask(int l, int r)//查询[l, r]中的最大值
    {
        int len = r - l + 1, k = 0;//len为待查区间长度
        while(1 << (k + 1) <= len)
            k++;
        //k代表原区间可以由两段长为2^k的区间覆盖
        return max(dp[l][k], dp[r - (1 << k) + 1][k]);
    }
  • 相关阅读:
    双系统中ubuntu的安装方法
    Android JNI和NDK学习(05)JNI真机调试
    Android JNI和NDK学习(04)NDK调试方法
    Intent总结01 Intent的结构
    Android JNI和NDK学习(07)JNI的常用API
    Android JNI和NDK学习(01)搭建NDK开发环境
    Android JNI和NDK学习(02)静态方式实现JNI
    Intent总结05 extra属性
    Intent总结06 category属性
    Android控件之TextView
  • 原文地址:https://www.cnblogs.com/sun-of-Ice/p/9418770.html
Copyright © 2020-2023  润新知