zoukankan      html  css  js  c++  java
  • st

    简介

    st表是解决RMQ问题(静态区间最值问题)的一种强有力的工具

    它可以做到O(nlogn)预处理,O(1)查询最值

    原因:倍增中的层数是log2(n),即2^j <= n, 所以d中元素不超过n*logn, 而每个元素都可以在常数的范围内计算完毕,所以总时间是O(nlogn)

    算法

    d(i,j)表示从 i 开始的,长度为2的 j 次方的一段元素中的最值,可用递推计算d

    找 i 的二进制数中的第一位

    d(i,j) = max/min(d(i, j-1), d(i+2^j, j-1) )

    //以最大值为例 (虽然RMQ的意思是最小值
    void st() {
    	for(int i = 1; i <= n; i++) f[i][0] = a[i];
    	for(int k = 1; (1<<k) <= n; k++) 
    		for(int i = 1; i+(1<<k)-1 <= n; i++) { //有边界
    			d[i][k] = max(d[i][k-1], d[i + (1<<(k-1))][k-1] );
    		}
    }
    
    int RMQ(int l, int R) {
    	int k = 0;//使k为满足2^k <=  R-L+1的最大整数,则以L开头, 以R结尾的两个长为2^k的区间合起来就覆盖了整个[L,R] 
    	while(( 1<<(k+1) ) <= R-L+1) k++;//如果写"(1<<k) <= R-L+1" , k还会再++,这样就可能越界了
    	return max(d[L][k], d[R-(1<<k)+1][k]); 
    }
    

    例题

    UVA-11235Frequent values

    tiyi:

    给出一个非降序的整数数组,你的任务是对于一系列询问(i,j),回答区间内出现最多的值的次数

    输入格式:

    多组数据

    每组: 第一行n,q(访问数)(1<=n,q<=100000) 。第二行n个非降序的整数(-100000<=a<=100000).以下q行,包括 i,j(合法的),输入结束标志为n==0

    分析

    因为整个数组是非降序的,所以可以用游程编码( 百度百科点这 )(其他博客点这)

    定义的数组和他们的作用相信你看了那些就懂了(或者看看刘汝佳蓝色书的P198页,那有详解)

    查询(L, R)结果为以下三部分最大值: L到L所在段的结点处的元素个数(即right[L]-L+1) , R到R所在段的开始处的元素个数(即R-left[R]+1), 中间第num[L]+1段到第num[R]-1段的count最大值

    注意特殊情况: 若L和R在同一段,


    代码:

    ps: 由于评测不了,我不知道我写的对不对,希望有人看见后能提醒下,我一定回来,谢谢

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define MAX 100000+99
    int val[MAX], countt[MAX], cnt;//分别表示第i段的数值和出现的次数 
    int num[MAX], left[MAX], right[MAX];//分别表示位置p所在边的编号,左右端点的位置 
    int a[MAX];
    int d[MAX][32];//log2(n)
    int isleft;//记录最左端的编号 
    
    int n,q;
    
    void init() {
    	for(int i = 1; i <= cnt; i++) d[i][0] = countt[i];
    	for(int k = 1; (1<<k) <= cnt; k++) 
    		for(int i = 1; i+(1<<k)-1 <= cnt; i++) 
    			d[i][k] = max(d[i][k-1], d[i + (1<<(k-1))][k-1]);
    }
    
    int RMQ(int L, int R) {
    	int k = 0;
    	while(( 1<<(k+1) ) <= R-L+1) k++;
    	return max(d[L][k], d[R-(1<<k)+1][k]);
    }
    
    int main() {
        while(scanf("%d%d",&n, &q) == 2) {
            for(int i = 1; i <= n; i++) {
                scanf("%d",&a[i]);
                if(a[i] > a[i-1]) {
                	cnt++;
                	val[cnt] = a[i];
                	countt[cnt]++;
                	num[i] = cnt;
                	left[i] = i;
                	for(int j = i-1; j >= 1 && num[j] == num[i-1]; j--) right[j] = i-1;//i-1所在的一段已完,处理right 
    				for(int j = i-1; j >= isleft && isleft != 0; j--) left[j] = isleft;//处理left 
    				isleft = i;
    			} else {
    				countt[cnt]++;
    				num[i] = cnt;
    			}
            }
    //		for(int i = 1; i <= n; i++) {
    //			printf("第%d个数所在边的编号, 左端点, 右端点: 
    ", i);
    //			printf("		%d   %d   %d
    ", num[i], left[i], right[i]);
    //		}//温馨提示: 注意差错哦 
    //		printf("当前最左端: %d  当前的段数: %d", isleft, cnt);
    		for(int i = n; i >= isleft; i--) left[i] = isleft, right[i] = n;
    //		for(int i = 1; i <= n; i++) {
    //			printf("第%d个数所在边的编号, 左端点, 右端点: 
    ", i);
    //			printf("		%d   %d   %d
    ", num[i], left[i], right[i]);
    //		}
            init();
            int L,R,ans = 0;
            for(int i = 1; i <= q; i++) {
            	scanf("%d%d",&L, &R);
            	if(num[L] == num[R]) ans = R-L+1;
          		else {
            		ans = max(R-left[R]+1, right[L]-L+1);
            		if(num[L]+1 < num[R]) ans = max(ans, RMQ(num[L]+1, num[R]-1));
         		}
         		printf("%d
    ", ans);
    		}
        }
        return 0;
    }
    

    这里还给出刘汝佳的std代码(我没仔细看....

    // UVa11235 Frequent Values
    // Rujia Liu
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    using namespace std;
    
    const int maxn = 100000 + 5;
    const int maxlog = 20;
    
    // 区间最*大*值
    struct RMQ {
      int d[maxn][maxlog];
      void init(const vector<int>& A) {
        int n = A.size();
        for(int i = 0; i < n; i++) d[i][0] = A[i];
        for(int j = 1; (1<<j) <= n; j++)
          for(int i = 0; i + (1<<j) - 1 < n; i++)
            d[i][j] = max(d[i][j-1], d[i + (1<<(j-1))][j-1]);
      }
    
      int query(int L, int R) {
        int k = 0;
        while((1<<(k+1)) <= R-L+1) k++; // 如果2^(k+1)<=R-L+1,那么k还可以加1
        return max(d[L][k], d[R-(1<<k)+1][k]);
      }
    };
    
    int a[maxn], num[maxn], left[maxn], right[maxn];
    RMQ rmq;
    int main() {
      int n, q;
      while(scanf("%d%d", &n, &q) == 2) {
        for(int i = 0; i < n; i++) scanf("%d", &a[i]);
        a[n] = a[n-1] + 1; // 哨兵
        int start = -1;
        vector<int> count;
        for(int i = 0; i <= n; i++) {
          if(i == 0 || a[i] > a[i-1]) { // 新段开始
            if(i > 0) {
              count.push_back(i - start);
              for(int j = start; j < i; j++) {
                num[j] = count.size() - 1; left[j] = start; right[j] = i-1;
              }
            }
            start = i;
          }
        }
        rmq.init(count);
        while(q--) {
          int L, R, ans;
          scanf("%d%d", &L, &R); L--; R--;
          if(num[L] == num[R]) ans = R-L+1;
          else {
            ans = max(R-left[R]+1, right[L]-L+1);
            if(num[L]+1 < num[R]) ans = max(ans, rmq.query(num[L]+1, num[R]-1));
          }
          printf("%d
    ", ans);
        }
      }
      return 0;
    }
    
  • 相关阅读:
    POJ3159 Candies —— 差分约束 spfa
    POJ1511 Invitation Cards —— 最短路spfa
    POJ1860 Currency Exchange —— spfa求正环
    POJ3259 Wormholes —— spfa求负环
    POJ3660 Cow Contest —— Floyd 传递闭包
    POJ3268 Silver Cow Party —— 最短路
    POJ1797 Heavy Transportation —— 最短路变形
    POJ2253 Frogger —— 最短路变形
    POJ1759 Garland —— 二分
    POJ3685 Matrix —— 二分
  • 原文地址:https://www.cnblogs.com/tyner/p/10982036.html
Copyright © 2011-2022 走看看