提高篇(1):RMQ问题与ST表
RMQ是英文Range Minimum/Maximum Query的缩写,是询问某个区间内的最值,这里讲一种解法:ST算法
ST算法通常用在要多次(10^6级别)询问区间最值的问题中,相比于线段树,它实现更简单,效率更高,但不支持修改,且一般只能维护最值。
ST算法实际上是动规,原理如下:
预处理:
一组数a[1]..a[n],设f[i][j]表示从a[i]到a[i+2^j-1]这个范围中的最值,元素个数为2^j个。
可以分成2部分,即从a[i]至a[i+2^(j-1)-1]与a[i+2^(j-1)]至a[i+2^j-1],所以
f[i][j]也可以分成f[i][j-1]与f[i+2^(j-1)][j-1],整个区间的最大值一定是左右两部分最大值的较大值,
于是可得状态转移方程:f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1]),边界条件为f[i][0]=a[i],这样即可在O(n log(n))的时间内预处理f数组。
询问:
若询问区间[l,r]的最大值,可以先求出最大的x,满足2^x<=r-l+1,那么区间[l,r]=[l,l+2^x-1]U[r-2^x+1,r],两个区间的元素个数都为2^x,
所以[l,r]中的最大值为max(f[l][x],f[r-2^x+1][x]),可以在O(1)内计算出来(对于m次询问,需要O(m)的时间复杂度)。这两个区间虽然有交集,但对最值没有影响,这就是ST算法只使用于区间最值的原因。
总结:
求区间[x,y]的最大值:
k=log2(y-x+1);
ans=max(f[x][k],f[y-2^k+1][k]);
技巧:
因为cmath库中的log2函数效率不高,所以为了提高速度,通常会使用O(N)的递推预处理出1~N这N种区间长度各自对应的k值。
具体地,设log[x]表示log2(x)向下取整,则log[x]=log[x/2]+1。这样总时间复杂度为log(n*log(n)+m+n)。
放一道例题:
平衡阵容(Balanced Lineup)
题目描述
每天,农夫 John 的N(1 <= N <= 50,000)头牛总是按同一序列排队. 有一天, John决定让一些牛们玩一场飞盘比赛. 他准备找一群在对列中为置连续的牛来进行比赛. 但是为了避免水平悬殊,牛的身高不应该相差太大. John 准备了Q (1 <= Q <= 180,000) 个可能的牛的选择和所有牛的身高 (1 <= 身高 <= 1,000,000). 他想知道每一组里面最高和最低的牛的身高差别. 注意: 在最大数据上, 输入和输出将占用大部分运行时间.
输入
输出
6
3
0
[参考程序]
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<climits> 5 #include<cmath> 6 #include<algorithm> 7 using namespace std; 8 9 const int N = 50005; 10 int FMAX[N][20], FMIN[N][20]; 11 12 void RMQ(int n) 13 { 14 for(int j = 1; j != 20; ++j) 15 { 16 for(int i = 1; i <= n; ++i) 17 { 18 if(i + (1 << j) - 1 <= n) 19 { 20 FMAX[i][j] = max(FMAX[i][j - 1], FMAX[i + (1 << (j - 1))][j - 1]); 21 FMIN[i][j] = min(FMIN[i][j - 1], FMIN[i + (1 << (j - 1))][j - 1]); 22 } 23 } 24 } 25 } 26 27 int main() 28 { 29 int num, query; 30 int a, b; 31 while(scanf("%d %d", &num, &query) != EOF) 32 { 33 for(int i = 1; i <= num; ++i) 34 { 35 scanf("%d", &FMAX[i][0]); 36 FMIN[i][0] = FMAX[i][0]; 37 } 38 RMQ(num); 39 while(query--) 40 { 41 scanf("%d%d", &a, &b); 42 int k = (int)(log(b - a + 1.0) / log(2.0)); 43 int maxsum = max(FMAX[a][k], FMAX[b - (1 << k) + 1][k]); 44 int minsum = min(FMIN[a][k], FMIN[b - (1 << k) + 1][k]); 45 printf("%d\n", maxsum - minsum); 46 } 47 } 48 return 0; 49 }
参考书籍:《信息学奥赛一本通·提高篇》