RMQ问题的ST算法解法

版权声明:转载请注明原址,有错误欢迎指出 https://blog.csdn.net/iwts_24/article/details/81706594

RMQ问题

       RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。

        以上来自百度百科。也就是说RMQ实际上是一个问题,而很多人说的RMQ算法实际上是RMQ问题的一种解法,也就是这篇博文主要介绍的ST算法。

RMQ问题的朴素解法与线段树解法

        朴素解法不再赘述了,非常非常暴力,理论上是不会有比赛能让你用朴素算法求解的。思路非常简单,遍历就完事了,当然也是非常慢的一种算法。

        利用线段树也是比较好的一种算法,区间询问很多人可能第一时间想到的就是利用线段树。当然,因为没有了求和,那么只用在构造线段树的时候进行改动就行了。线段树的结点存放线段最大值/最小值,也就在线段树模板的基础上更改几行代码而已。区间搜索同理。

ST算法

        ST算法本质上是动态规划的思想,状态定义为:F(i,j),从第i个数起连续2^j个数中的最大值。那么ST算法就分为2步:

1.在O(nlogn)的时间复杂度内构造出dp数组。

2.进行查询。

构造dp数组

        首先,状态的定义上面已经给出了:F(i,j),从第i个数起连续2^j个数中的最大值。假设有原序列A[I],那么可以得到初始值:dp[i][0] = A[i]。

        状态转移方程为:

dp(i,j) = max(dp[i,j-1],dp[i + 2^(j-1),j-1]);

实际上,这样是把dp(i,j)这个状态给平均分成了2部分。如果我们假设序列A[i],i=8。那么我们可以得到i = 1,j = 3。(总长度为8,那么i = 1是从第一个数起,要连续2^j个数,即log2n = j,n = 8,即j = 3)将i = 1、j = 3代入转移方程,可以得到恰好是[1,4]与[5,8],平分了[1,8]。所以有代码:

void st(int n) {
    for(int i = 1;i <= n;i++){
        cin >> dp[i][0];
        // 节省时间,实际上初始化dp应该为:dp[i][0] = A[i];
    }
    for(int j = 1;(1 << j) <= n;j++){
        for(int i = 1;i + (1 << j) - 1 <= n; i++){
            dp[i][j] = Max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
        }
    }
}

这里参考了大佬的写法,我之前的写法就是很简单的写法,没有用位运算,这里给不会位运算的同学解释一下:1 << j,就是1向左移j位,二进制的数每向左移1位就*2,那么移动j位实际上就是2^j了。这里的2个for循环都是为了防止超出范围。

进行查询

        可以看到,上面的求法得到的dp数组不是完全区间覆盖,不能直接查询区间。在区间[1,8]中,能直接得到答案的区间只有[1,1]、[1,2]、[1,4]、[1,8]、[2,2]..以此类推。那么我们仍然分开考虑,这里令k = log2( j - i + 1),则有:

ans(i, j) = max{dp[i,k],dp[ j - 2 ^ k + 1, k]

        仍然,代入具体数值就能理解了。代码如下:

int rmq(int l,int r) {
    int k = 0;
    while((1 << (k + 1)) <= r - l + 1) k++;
    return Max(dp[l][k], dp[r - (1 << k) + 1][k]);
    // 具体找最大值还是最小值,只用更改Max还Min就可以了,注意 st 函数里也要修改
}

这里也是,之前对于k值的计算是直接算的,某大佬利用while循环,因为k值要求是int类型,所以循环结束获得的就是k值。

最大值最小值变通

        将所有Max函数更换成Min函数就是从最大值变成最小值了,很简单也很容易理解。

参考例题

poj-3264 Balanced Lineup

基本是模板题了。

猜你喜欢

转载自blog.csdn.net/iwts_24/article/details/81706594