ST算法解决RMQ问题

版权声明:欢迎转载,请注明此博客地址。 https://blog.csdn.net/Ever_glow/article/details/86577405

RMQ问题

简单说就是求区间最值问题,若是简单的单次询问或者是区间长度很短的询问,可以用暴力的方法来实现,但面对大数据的时候此方法必然超时,这里介绍O(nlogn)预处理,O(1)查询的ST算法。


 ST算法

ST的高效在于O(nlogn)的时间预处理,O(1)的时间来查询。其主要思想就是将所求的区间化为两个小区间,这两个区间的长度正好是2的k次幂,总长度正好覆盖[l,r],得到的结果就是所求答案。

首先要清楚为什么是分成两段,而不是3段,4段,首先分成两段的话是好写的,比那些处理3段以及n段毕竟简单。在logn的时间复杂度下,以2为底或者以其他数为底的话,时间复杂度差距也不是那么明显,所以选择了分成两段,便于位运算以及其他方面的简便操作。


过程

首先确定dp[i,j]表示以第i个数开始,长度为2^j的区间长度的区间最值

易知dp[i][0] = s[i].即表示以i为起点,长度为1的区间的最值就是当前的数值。

然后可以考虑从当前一个点,以2的指数倍向一侧扩展,可以得到以当前点为起点,延申到边界的数组最值。


实现

考虑O(nlogn)的时间复杂度,即以当前点为起始点,向右侧开始维护最值,从2^1开始,因为2^k一定是偶数,所以一定可以将2^k拆分为两个2^(k-1),即将区间[l,r]分为[l,l+2^(k-1)-1]跟[l+2^(k-1),r],将区间长度从小到大开始枚举,这样一定可以得到以当前起点一直到n的的每一个区间段内的最大值。

ll maxx[N][20],minn[N][20],n,s[N];
void init()
{
    for(int i = 1; i <= n; i++)
        maxx[i][0] = minn[i][0] = s[i];
    int k = (int)(log(n*1.0)/log(2.0));
    for(int i = 1; i <= k; i++)
        for(int j = 1; j <= n; j++)
        {
            maxx[j][i] = maxx[j][i-1];
            if(j+(1<<(i-1)) <= n) maxx[j][i] = max(maxx[j][i],maxx[j+(1<<(i-1))][i-1]);
            minn[j][i] = minn[j][i-1];
            if(j+(1<<(i-1)) <= n) minn[j][i] = min(minn[j][i],minn[j+(1<<(i-1))][i-1]);
        }
}

查询

对于当前区间[l,r],如何表示为以某个点为起点,长度是2^k的区间。

k=[log2(j-i+1)],则有:RMQ(i, j) = max(dp[i,k], dp[j-2^k+1,k])

考虑k的取值问题,k一定可以将整个区间都覆盖吗?

[l,r]分成2份,一部分是[l,i+2^(k-1)-1],一部分是[j-2^(k-1)+1,r],若是要完成覆盖,则应该有j-2^(k-1)+1 <= i+2^(k-1)-1,移项合并同类项得到2^k >= j-i+1,可以验证上述k取值是成立的。

分成两个区间后,在两个区间取最值就可以了。

ll rmq_min(ll l,ll r)
{
    ll m = (ll)(log((r-l+1)*1.0)/log(2.0));
    return min(minn[l][m],minn[r-(1<<m)+1][m]);
}
ll rmq_max(ll l,ll r)
{
    ll m = (ll)(log((r-l+1)*1.0)/log(2.0));
    return max(maxx[l][m],maxx[r-(1<<m)+1][m]);
}

完整代码

/*
Look at the star
Look at the shine for U
*/
#include<bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define sl(x) scanf("%lld",&x)
using namespace std;
const int N = 1e6+5;
const int mod = 1e9+7;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
ll inv(ll b){if(b==1)return 1; return (mod-mod/b)*inv(mod%b)%mod;}
ll fpow(ll n,ll k){ll r=1;for(;k;k>>=1){if(k&1)r=r*n%mod;n=n*n%mod;}return r;}
ll maxx[N][20],minn[N][20],n,s[N];
void init()
{
    for(int i = 1; i <= n; i++)
        maxx[i][0] = minn[i][0] = s[i];
    int k = (int)(log(n*1.0)/log(2.0));
    for(int i = 1; i <= k; i++)
        for(int j = 1; j <= n; j++)
        {
            maxx[j][i] = maxx[j][i-1];
            if(j+(1<<(i-1)) <= n) maxx[j][i] = max(maxx[j][i],maxx[j+(1<<(i-1))][i-1]);
            minn[j][i] = minn[j][i-1];
            if(j+(1<<(i-1)) <= n) minn[j][i] = min(minn[j][i],minn[j+(1<<(i-1))][i-1]);
        }
}
ll rmq_min(ll l,ll r)
{
    ll m = (ll)(log((r-l+1)*1.0)/log(2.0));
    return min(minn[l][m],minn[r-(1<<m)+1][m]);
}
ll rmq_max(ll l,ll r)
{
    ll m = (ll)(log((r-l+1)*1.0)/log(2.0));
    return max(maxx[l][m],maxx[r-(1<<m)+1][m]);
}
int main()
{
    ll i,j,k,q,l,r;
    sl(n);sl(q);
    for(i = 1;i <= n;i++)
        sl(s[i]);
    init();
    while(q--)
    {
        sl(l);sl(r);
        ll mx = rmq_max(l,r);
        ll mn = rmq_min(l,r);
        printf("%lld %lld\n",mx,mn);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Ever_glow/article/details/86577405