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

    /*
    RMQ(Range Minimum/Maximum Query)问题:
       RMQ问题是求给定区间中的最值问题。当然,最简单的算法是O(n)的,但是对于查询次数很多(设置多大100万次),O(n)的算法效率不够。可以用线段树将算法优化到O(logn)(在线段树中保存线段的最值)。不过,Sparse_Table算法才是最好的:它可以在O(nlogn)的预处理以后实现O(1)的查询效率。下面把Sparse Table算法分成预处理和查询两部分来说明(以求最小值为例)。
    
    预处理:
    预处理使用DP的思想,f(i, j)表示[i, i+2^j - 1]区间中的最小值,我们可以开辟一个数组专门来保存f(i, j)的值。
    例如,f(0, 0)表示[0,0]之间的最小值,就是num[0], f(0, 2)表示[0, 3]之间的最小值, f(2, 4)表示[2, 17]之间的最小值
    注意, 因为f(i, j)可以由f(i, j - 1)和f(i+2^(j-1), j-1)导出, 而递推的初值(所有的f(i, 0) = i)都是已知的
    所以我们可以采用自底向上的算法递推地给出所有符合条件的f(i, j)的值。
    
    查询:
    假设要查询从m到n这一段的最小值, 那么我们先求出一个最大的k, 使得k满足2^k <= (n - m + 1).
    于是我们就可以把[m, n]分成两个(部分重叠的)长度为2^k的区间: [m, m+2^k-1], [n-2^k+1, n];
    而我们之前已经求出了f(m, k)为[m, m+2^k-1]的最小值, f(n-2^k+1, k)为[n-2^k+1, n]的最小值
    我们只要返回其中更小的那个, 就是我们想要的答案, 这个算法的时间复杂度是O(1)的.
    例如, rmq(0, 11) = min(f(0, 3), f(4, 3))
    */
    #include<iostream>
    #include<cmath>
    using namespace std;
    #define MAXN 1000000
    #define mmin(a, b)   ((a)<=(b)?(a):(b))
    #define mmax(a, b)   ((a)>=(b)?(a):(b))
    
    int num[MAXN];
    int f1[MAXN][100];
    int f2[MAXN][100];
    
    //测试输出所有的f(i, j)
    void dump(int n)
    { 
        int i, j;
        for(i = 0; i < n; i++)
        {
            for(j = 0; i + (1<<j) - 1 < n; j++)
            {
                printf("f[%d, %d] = %d\t", i, j, f1[i][j]);
            }
            printf("\n");
        }
        for(i = 0; i < n; i++)
           printf("%d ", num[i]);
        printf("\n");
        for(i = 0; i < n; i++)
        {
            for(j = 0; i + (1<<j) - 1 < n; j++)
            {
                printf("f[%d, %d] = %d\t", i, j, f2[i][j]);
            }
            printf("\n");
        }
        for(i = 0; i < n; i++)
            printf("%d ", num[i]);
        printf("\n");
    }
    
    //sparse table算法
    void st(int n)
    { 
        int i, j, k, m;
        k = (int) (log((double)n) / log(2.0)); 
        for(i = 0; i < n; i++) 
        {
            f1[i][0] = num[i]; //递推的初值
            f2[i][0] = num[i];
        }
        for(j = 1; j <= k; j++)
        { //自底向上递推
            for(i = 0; i + (1 << j) - 1 < n; i++)
            {
                m = i + (1 << (j - 1)); //求出中间的那个值
                f1[i][j] = mmax(f1[i][j-1], f1[m][j-1]);
                f2[i][j] = mmin(f2[i][j-1], f2[m][j-1]);
            }
        }
    }
    
    //查询i和j之间的最值,注意i是从0开始的
    void rmq(int i, int j) 
    { 
        int k = (int)(log(double(j-i+1)) / log(2.0)), t1, t2; //用对2去对数的方法求出k
        t1 = mmax(f1[i][k], f1[j - (1<<k) + 1][k]);
        t2 = mmin(f2[i][k], f2[j - (1<<k) + 1][k]);
        printf("%d\n",t1 - t2);
    }
    
    int main()
    {
        int i,N,Q,A,B;
        scanf("%d %d", &N, &Q);
        for (i = 0; i < N; ++i)
        {
            scanf("%d", num+i);
        }
    
        st(N); //初始化
        //dump(N); //测试输出所有f(i, j)
        while(Q--)
        {
            scanf("%d %d",&A,&B);
            rmq(A-1, B-1);
        }
        return 0;
    }


    [i, i + 2 ^(j - 1) - 1]与[2 ^(j - 1), i + 2 ^ j - 1]长度均为2 ^ (j - 1)


    状态转移方程:f[i][j] = max(f[i][j-1], f[i + 2 ^(j-1), j - 1]);



  • 相关阅读:
    085 Maximal Rectangle 最大矩形
    084 Largest Rectangle in Histogram 柱状图中最大的矩形
    083 Remove Duplicates from Sorted List 有序链表中删除重复的结点
    082 Remove Duplicates from Sorted List II 有序的链表删除重复的结点 II
    081 Search in Rotated Sorted Array II 搜索旋转排序数组 ||
    080 Remove Duplicates from Sorted Array II 从排序阵列中删除重复 II
    079 Word Search 单词搜索
    078 Subsets 子集
    bzoj2326: [HNOI2011]数学作业
    bzoj2152: 聪聪可可
  • 原文地址:https://www.cnblogs.com/lgh1992314/p/5835008.html
Copyright © 2011-2022 走看看