RMQ模板

RMQ 即范围最小值问题 (Range Minimum Query)。给出一个 n 个元素的数组,设计一个数据结构,支持查询操作 Query(L, R):计算该区间内的最小值。

如果用朴素的算法的话,每一次一个循环求解,那时间复杂度就达到了 O(mn),显然不够快。在实践中,最常用的是 Tarjan 的 Sparse - Table(st 表)算法,主要功能是解决静态区间最值问题。它用 O(nlogn)预处理,而查询只用 O(1),且常数很小。

这个算法具体是这么写的:开一个二维数组 dp[i][j] ,表示从 i 开始的,长度为 2 ^ j 的一段元素的最小值,这样就可以用递推的方法计算 dp[i][j] : dp[i][j] = min(dp[i][j - 1], dp[i + 2 ^ (j - 1)][j - 1])。原理如下图所示:

可以看出,这是利用归并的思想。

因为2^j <= n,因此dp数组的元素个数不超过 nlogn 个,而且每一项都可以在常数时间计算完毕,所以时间复杂度为 O(nlogn)。上代码

 1 void RMQ_init()
 2 {
 3     for(int i = 1; i <= n; ++i) dp[i][0] = a[i];    //此时长度为1,个只有一个元素。 
 4     for(int j = 1; (1 << j) <= n; ++j)    
 5     /*长度一定要放在外层循环,这样才能保证每一个元素都被更新到 ,
 6     跟区间dp原理一样*/
 7         for(int i = 1; i + (1 << j) - 1 <= n; ++i)
 8         {
 9             dp[i][j] = min(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
10         }
11 }

 

查询也很简单,令 k 满足 2 ^ k <= R - L + 1 的最大整数,则以 L 开头,R 结尾的两个长度为2 ^ k 的区间合起来就覆盖了要查询的区间 [L, R]。由于是取最小值,因此两个子区间有重叠的部分也没关系,代码如下

1 int query(int L, int R)
2 {
3     int k = 0;
4     while(1 << (k + 1) <= R - L + 1) k++;    
5     //注意是 k + 1,而不是k,因为这么写的话,符合条件k还要再+1,就应返回dp[L][k - 1]了 
6     return min(dp[L][k], dp[R - (1 << k) + 1][k]);
7 }

不过这样查询的时间复杂度就是 O(logn)。只要初始化 k,开一个数组 que[i],代表长度为 i 时 k 的取值,查询的复杂度就能达到 O(1)了。

int k = 0;
for(int i = 1; i <= n; ++i) {
    if ((1 << k) <= i) k++; 
    que[i] = k - 1;
}

void query(int L, int R)
{
    int k = que[R - L + 1];
    return min(dp[L][k], dp[R - (1 << k) + 1][k]);
}

 

posted @ 2018-02-16 14:46  mrclr  阅读(304)  评论(0编辑  收藏  举报