最小白的RMQ,求第i到第j的最大值和最小值差值。采用SparseTable算法,其实就是一种相当高效的打表,空间范围nlogn,每次查询的时间只需要常数。 假如是一般的打表,对每组i和j,记录下第i到第j的最大值和最小值,需要的空间为N2,N增大时显然无法满足要求。因此考虑改进算法,适当扩大时间,缩小空间,使之平衡。 ST采用的方法相当高效,二分划分,把每个i的所辖范围划分成1,2,4,8,16的小块,和操作系统中伙伴系统的划分倒有几分类似。 以最大值为例。F(i,j)表示以i位元素开始,连续2i个元素中的最大值。初值设所有的F(i,0)即为i的值。 那么,F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1])。这个等式不难推出。 然后计算l和r区间最大值时,只要划分成两块小且互相覆盖的区间就可以了。 第一次做的时候计算最后区间出了点差错,百思不得其解。为什么是(ln(r-l+1)/ln(2));r-l计算莫非会出精度错误? #include <cstdio>#include <cmath>#include <string> int N, Q, a[50000], s[16][50000], t[16][50000], A, B, L; #define max( x, y ) ( (x) > (y) ? (x) : (y) )#define min( x, y ) ( (x) < (y) ? (x) : (y) ) void pt (){ int i, j; for ( i = 0; i <= L; i ++ ) { for ( j = 0; j < N; j ++ ) { printf ( "%d ", t[i][j] ); } printf ( "\n" ); }} void init (){ memset ( s, 0, sizeof ( s ) ); memset ( t, 0, sizeof ( t ) ); scanf ( "%d %d", &N, &Q ); int i, j; L = log ( double ( N ) ) / log ( 2.0 ); printf ( "%d\n", L ); for ( i = 0; i < N; i ++ ) { scanf ( "%d", &a[i] ); } for ( j = 0; j < N; j ++ ) { s[0][j] = t[0][j] = a[j]; } for ( i = 1; i <= L; i ++ ) { for ( j = 0; j < N; j ++ ) { int j1 = j + ( 1 << ( i - 1 ) ); if ( j1 < N ) { s[i][j] = min ( s[i - 1][j], s[i - 1][j1] ); t[i][j] = max ( t[i - 1][j], t[i - 1][j1] ); } else { s[i][j] = s[i - 1][j]; t[i][j] = t[i - 1][j]; } } } //pt ();} int rmq ( int l, int r ){ int m = ( log ( double ( r - l + 1 ) ) / log ( 2.0 ) ); int l0 = l, r0 = r - ( 1 << m ) + 1; //printf ( "%d %d %d\n", m, l0, r0 ); int mx = max ( t[m][l0], t[m][r0] ); int mn = min ( s[m][l0], s[m][r0] ); return mx - mn;} void calc (){ int i; for ( i = 0; i < Q; i ++ ) { scanf ( "%d %d", &A, &B ); A --, B --; if ( A == B ) { printf ( "0\n" ); continue; } printf ( "%d\n", rmq ( A, B ) ); }} int main (){ //freopen ( "t.txt", "r", stdin ); init (); calc (); return 0;}

评论