zoukankan      html  css  js  c++  java
  • RMQ问题+ST算法

    一、相关定义

    RMQ问题

    • 求给定区间的最值;
    • 一般题目给定许多询问区间。

    常见问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大值。

    解决方法

    1. 暴力搜索    O(n)-O(n) 
    2. 线段树  O(n)-O(q*logn)
    3. ST算法       O(n*logn)-O(1)

    二、ST(Sparse Table)算法

    本节介绍了一种比较高效的在线算法(ST算法)解决RMQ问题。

    ST算法

    • 是一个非常有名的在线处理RMQ问题的算法;
    • 基于DP和位运算符;
    • 主要过程为:预处理+查询;
    • 时间复杂度:在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

    数据说明:

    • dp[i][j]    表示从第i位开始,到第i+2j-1位的最大/小值;(即从第i个数起连续2j个数中的最大/小值)

    求dp[i][j]的时候,可以把它分成两部分,第一部分从 i 到 i+2j-1-1,第二部分从 i+2j-1 到 i + 2j - 1 。显然二进制数后一个是前一个的两倍,那么可以把区间[i,i+2j] 通过2j-1分成相等的两部分(2j个数,则一定可以二等分),dp[i][j]就是这两段各自最大值中的最大值。 那么转移方程很容易就写出来了。

    • mm[i][j] = max( mm[ i ][ j - 1] , mm[ i + (1 << ( j - 1))][ j - 1] )

    DP三要素:

    1.  初始值:dp[i][0] = A[i]
    2. 状态:dp[i][j] 表示从第i位开始,到第i+2j-1位的最大/小值;
    3. 状态转移方程:mm[i][j] = max( mm[ i ][ j - 1] , mm[ i + (1 << ( j - 1))][ j - 1] )

    三、算法模板

    【预处理】——DP(动态规划)

    模板代码(以求最大值为例):

    void rmq_init(int n)
    {
        for(int i=1;i<=n;i++)
            mm[i][0]=mi[i][0]=a[i];
        for(int j=1;(1<<j)<=n;j++)		//注意循环的顺序,外层是j,内层是i
        {
            for(int i=1;i+(1<<j)-1<=n;i++)
            {
    	    mm[i][j]=max(mm[i][j-1],mm[i+(1<<(j-1))][j-1]);
            }
        }
    }

     这里我们需要注意的是循环的顺序,我们发现外层是j,内层是i,这是为什么呢?可以是i在外,j在内吗?

    答案是不可以。因为我们需要理解这个状态转移方程的意义。不懂,请点击

    【查询】——O(1)

    查询的时候对于任意一个区间 l -- r ,我们同样可以得到区间长度 len = (r - l + 1)。

    那么我们这里用满足不等式“2k<=len”的 k 把区间分成可以交叉的两部分:①[l,l+2k-1];②[r -2k+1,r] 。

     例子:查询5,6,7,8,9,我们可以查询56786789)。

    因为这个区间的长度为 r - l + 1,所以我们可以取k=log2( r - l + 1),则有:RMQ(A, l, r)=max{mm[l , k], mm[ r - 2k + 1, k]}。

    举例说明,要求区间[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2] , F[8 - 22 + 1, 2]) = max(F[2, 2] , F[5, 2]);

    在这里我们也需要注意一个地方,就是<<运算符比+/-运算符的优先级低。

    代码:

    int rmq(int l,int r)
    {
        int k=0;
        while((1<<(k+1))<=r-l+1)
            k++;
      //printf("%d %d %d %d
    ",l,l+(1<<k),r-(1<<k)+1,r-(1<<k)+1+(1<<k));
        int ans1=max(mm[l][k],mm[r-(1<<k)+1][k]);
        int ans2=min(mm[l][k],mm[r-(1<<k)+1][k]);
        return ans1-ans2;          //返回区间最大值与最小值的差
    }

    四、沙场练兵

    题目:poj 3264 Balanced Lineup

    题目:士兵杀敌(三) 

    代码:

    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    
    const int N = 100010;
    int maxsum[N][20], minsum[N][20];
    
    void RMQ(int num) //预处理->O(nlogn)
    {
    	for(int j = 1; j < 20; ++j)
    		for(int i = 1; i <= num; ++i)
    			if(i + (1 << j) - 1 <= num)
    			{
    				maxsum[i][j] = max(maxsum[i][j - 1], maxsum[i + (1 << (j - 1))][j - 1]);
    				minsum[i][j] = min(minsum[i][j - 1], minsum[i + (1 << (j - 1))][j - 1]);
    			}
    }
    
    int main()
    {
    	int num, query;
    	int src, des;
    	scanf("%d %d", &num, &query);
    		for(int i = 1; i <= num; ++i) //输入信息处理
    		{
    			scanf("%d", &maxsum[i][0]);
    			minsum[i][0] = maxsum[i][0];
    		}
    		RMQ(num);
    		while(query--) //O(1)查询
    		{
    			scanf("%d %d", &src, &des);
    			int k = (int)(log(des - src + 1.0) / log(2.0));
    			int maxres = max(maxsum[src][k], maxsum[des - (1 << k) + 1][k]);
    			int minres = min(minsum[src][k], minsum[des - (1 << k) + 1][k]);
    			printf("%d
    ", maxres - minres);
    		}
    	return 0;
    }
    

     优化后代码:

    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    
    const int N = 100010;
    int maxsum[20][N], minsum[20][N]; //优化1
    
    void RMQ(int num) //预处理->O(nlogn)
    {
    	for(int i = 1; i != 20; ++i)
    		for(int j = 1; j <= num; ++j)
    			if(j + (1 << i) - 1 <= num)
    			{
    				maxsum[i][j] = max(maxsum[i - 1][j], maxsum[i - 1][j + (1 << i >> 1)]); //优化2
    				minsum[i][j] = min(minsum[i - 1][j], minsum[i - 1][j + (1 << i >> 1)]);
    			}
    }
    
    int main()
    {
    	int num, query;
    	int src, des;
    	scanf("%d %d", &num, &query);
    		for(int i = 1; i <= num; ++i) //输入信息处理
    		{
    			scanf("%d", &maxsum[0][i]);
    			minsum[0][i] = maxsum[0][i];
    		}
    		RMQ(num);
    		while(query--) //O(1)查询
    		{
    			scanf("%d %d", &src, &des);
    			int k = (int)(log(des - src + 1.0) / log(2.0));
    			int maxres = max(maxsum[k][src], maxsum[k][des - (1 << k) + 1]);
    			int minres = min(minsum[k][src], minsum[k][des - (1 << k) + 1]);
    			printf("%d
    ", maxres - minres);
    		}
    	return 0;
    }
    

    优化1:数组由F[N][20]变为F[20][N]

    因为数组的地址为a + i + j,对应上面数组,我们需要先循环N的部分,所以

    如果是第一种,我们计算时因为i不断变化,我们就需要计算a + i + j

    如果是第二种,我们计算时a + i不变,只需要改变j

    优化2:位运算

  • 相关阅读:
    vfpConn
    OAuth2.0
    开源日志组件ELMAH
    c# 动态数组 ArrayList
    OleDbHelper类
    系统权限管理框架
    Log4net数据表
    C#创建DBF自由库
    数字化校园passport
    使用 StateServer 保存 Session 解决 Session过期,登陆过期问题。
  • 原文地址:https://www.cnblogs.com/xzxl/p/7237540.html
Copyright © 2011-2022 走看看