rmq入门

对于求区间最大最小值,我们自然而然就想到了一个O(n)时间复杂度的算法,但是如果询问有很多呢?这样必然超时。当然我们可以用线段树来解,使得每一次查询的时间降到log(n),但是对于RMQ算法,只要我们做了些预处理,之后的查询我们仅需要O(1)的时间。Sparse_Table算法是解决RMQ问题的一类较好的算法,属于一种在线算法,至于什么叫在线什么叫离线,先简单介绍一下。

在线算法:在计算机科学中,一个在线算法是指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。

离线算法:在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果。例如,选择排序在排序前就需要知道所有待排序元素,然而插入排序就不必了。

简单的概括一下 在线算法就是说程序先把预处理工作做好,对于之后的查询,可以很快给你答复。离线算法就是你先把需求告诉程序,他一次性给你解决。

好了,下面来讲解Sparse_Table算法

1.求最值数组

Sparse_Table算法的预处理就是一个动态规划的思想。

设数组maxn[i][j] 表示给定的数组从下标i开始,长度为2^j的区间最大值(最小值一样)也就是arr[i]----arr[i+2^j-1]这个区间的最大值。

于是我们可以写出这样一个动态转移方程maxn[i][j] = max(maxn[ i ][ j-1 ],maxn[ i+2^(j-1) ][ j-1 ]) 看懂了么?

其实就是把区间【i ,i+2^j -1】分成两段,一段是【i,i+2^(j-1)-1】 和【i+2^(j-1),i+2^j】(一直记住二维数组后面一维表示的是区间的长度2^j)

那么对于maxn[i][j]当j等于0,也就是区间长度为1的最大值显然就有maxn[i][0] = arr[i];

到此我们就可以写出Sparse_Table的预处理部分了

 

void st(int n)
{
    int term=(int)floor(log2(double(n)));// 因为区间的最长长度是2^tem==n嘛  
    for(int i=1;i<=n;i++) mn[i][0]=mx[i][0]=a[i]; // 初始值
    for(int j=1;j<=term;j++) 
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
            mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
        }
    }
}

 

我们看看这个动态规划方程是怎么求解这个maxn,minn数组的

要求区间长度为2的肯定要先求出区间长度为1的嘛  比如区间[1,2]只要在区间[1,1]   [2,2]中取最值嘛 长度为4的肯定要先算出长度为2的嘛 比如求区间[6,9]的最值只要在区间[6,7] [8,9]中取最值嘛。。。。。。

所以最外层的循环就肯定是区间的长度2^j次方咯 里面的循环应该都看得懂吧。

 

2.查询

这个最值区间的数组求出来了,下面就是查询了 

比如要查询区间[a,b]的最值  怎么求呢?

注意到我们的最值数组存的都是区间长度为2^k(k=0,1,2,3.....)次方的最值

所以对于区间[a,b] 我们肯定要划分为两个区间长度是2^x  2^y的区间才可以直接利用我们得到的最值数组来求最值嘛

这里有两个未知数不好求,我们可以直接取k,对于k满足a+2^k-1=b  k=log2(b-a+1) (注意这里不是a+2^k=b 还是那句话,始终记得2^k是区间的元素的个数) 那么区间a,b的最大值不就是max(maxn[a][k],maxn[b-2^k+1][k])比如对于区间长度为4的[3,6]求出k==2 于是最大值就是区间max(【3,6】,【3,6】)当然我们不能能保证log2(a-b+1)就一定能得到一个整数,但是这不要紧,比如对于区间长度为 5的【3,7】我们对log2(7-3+1)取整得到2,于是最大值就在

max(【3,6】,【4,7】),max函数里面前面那个maxn[a][k]就保证了我们的求最值的区间以a开始,后面那个maxn[b-2^k+1][k]就保证了我们必然能够以b为尾

 

int query(int l,int r) // 这里返回的是最大值减去最小值
{
    int k=log2(r-l+1);
    int maxtemp=max(mx[l][k],mx[r-(1<<k)+1][k]);
    int mintemp=min(mn[l][k],mn[r-(1<<k)+1][k]);
    return maxtemp-mintemp;
}

简单梳理一下 ,st预处理利用二进制的思想把区间划分为两部分降低复杂度。

由于记录的都是2的n次方长度的最值,对于一般的区间最值我们还需要做一些简单的处理来达到目的

poj 3264

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int a[100010];
int mx[100010][80],mn[10010][80];
void st(int n)
{
    int term=(int)floor(log2(double(n)));
    for(int i=1;i<=n;i++) mn[i][0]=mx[i][0]=a[i];
    for(int j=1;j<=term;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
            mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
        }
    }
}
int query(int l,int r)
{
    int k=log2(r-l+1);
    int maxtemp=max(mx[l][k],mx[r-(1<<k)+1][k]);
    int mintemp=min(mn[l][k],mn[r-(1<<k)+1][k]);
    return maxtemp-mintemp;
}
int main()
{
    int n,q;
    scanf("%d %d",&n,&q);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    st(n);
    while(q--)
    {
        int l,r;
        scanf("%d %d",&l,&r);
        cout<<query(l,r)<<endl;
    }
    return 0;
}

 

posted @ 2018-01-22 20:35  猪突猛进!!!  阅读(164)  评论(0编辑  收藏  举报