范围最小值问题(Range Minimum Query)是指:给定一个n个元素的数组A[1],A[2]...A[n]。设计一个数据结构,支持查询操作Query(L,R):计算min{A[L],A[L+1]...A[R]}。
该问题在实践中常用Tarjan的Sparse-Table算法。它的预处理时间是O(N*logN),但查询只需要O(1),而且常数非常小。最重要的是,这个算法非常好写,而且不易写错。
(1)原理:该算法利用了分治法的思想,令d(i,j)表示从i开始的,长度为2^j的一段元素中的最小值。则不难发现如下公式成立:
d(i,j)=min{d(i,j-1),d(i+2^(j-1),j-1)}
注意到2^i≤n,因此d数组的元素个数不超过n*logn个,而每一项都能在常数时间内算完。因此总的时间复杂度是O(N*logN)。
#define N 1000 int d[N][N]; vector<int>A; #define INF 100000000 void RMQ_init(const vector<int>&A)//初始化操作,所有元素放到vector中 { for (int i = 0; i < N;i++) for (int j = 0; j < N; j++) d[i][j] = INF; 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] = min(d[i][j - 1], d[i + (1 << (j - 1))][i - 1]); }
查询操作也很简单,令k为满足2^k≤R-L+1的最大整数,则以L开头,以R结尾的两个长度为2^k的区间合起来即覆盖了查询区间[L,R]。由于是最小值,有些元素重复考虑了也没有关系。
int RMQ(int L, int R) { int k = 0; while ((1 << (k + 1)) <= R - L + 1)k++; return min(d[L][k], d[R - (1 << k) + 1][k]); }
更简洁的写法如下:
int RMQ(int L, int R) { int k = log((double)(R - L + 1)) / log(2.0); return min(d[L][k], d[R - (1 << k) + 1][k]); }