RMQ问题的ST算法
ST(Sparse Table)算法的基本思想是,预先计算从起点A[i]开始长度为2的j次方(j=0,1...logn)的区间的最小值,然后在查询时将任何一个区间A[i..j]划分为两个预处理好的可能重叠的区间,取这两个重叠区间的最小值。
在预处理阶段,从起点A[i]开始,任何一个长度为2^j的区间都可以划分为两个长度2^(j-1)的区间,其中第一个区间的范围为:i...i+2^(j-1)-1;第二个区间的范围为:i+2^(j-1)...i+2^j-1。用M[i,j]表示从A[i]开始,长度为2^j的区间(即A[i]...A[i+2^j-1])最小值对应的下标,那么A[M[i,j]] = min{A[i...i+2^(j-1)-1], A[i+2^(j-1)...i+2^j-1]}。 利用DP思想,先计算M[i,j-1]的值,然后计算M[i,j]的值。
在 查询阶段,任何区间A[i..j]的长度d=j-i+1,令k=floor(logd),那么该区间可以被两个长度为2^k的子区间完全覆盖,这两个长度 为2^k的区间可以有重叠。由于这两个区间已经在预处理中求得最小值,因此可以取二者的最小值得到A[i..j]的最小值。
ST算法预处理阶段的复杂度为O(nlogn),查询阶段的复杂度为O(1)。
实现:
/** * * Using ST(Sparse Table) algorithm to solve RMQ problem * time complexity: <O(nlogn),O(1)> * * * Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/) * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) * * @author ljs * 2011-08-02 * */ public class RMQ_ST { //ST: O(nlogn) for preprocessing public int[][] preprocess(int[] A){ int n = A.length; //floor value int maxJ=(int)(Math.log(n)/Math.log(2)); int[][] M = new int[n][maxJ+1]; //initial condition for dynamic programming: the RMQ for interval length=1 for (int i = 0; i < n; i++) M[i][0] = i; //dynamic programming: compute values from smaller(j=1) to bigger intervals for (int j = 1; j<=maxJ; j++){ for (int i = 0; i + (1 << j) - 1 < n; i++){ int nexti = i + (1 << (j - 1)); if (A[M[i][j - 1]] <= A[M[nexti][j - 1]]) M[i][j] = M[i][j - 1]; else M[i][j] = M[nexti][j - 1]; } } return M; } //ST: O(1) for querying public int query(int[] A,int[][] M,int i,int j){ if(j<i){ //swap i and j int tmp=i;i=j;j=tmp; } int k = (int)(Math.log(j-i+1)/Math.log(2)); //the first interval int mina = M[i][k]; int minb = M[j-(1<<k)+1][k]; if(A[mina]<=A[minb]) return mina; else return minb; } public static void main(String[] args) { int[] A=new int[]{0,1,2,3,7,1,9,2,8,6}; RMQ_ST st = new RMQ_ST(); int[][] M = st.preprocess(A); System.out.format("%n***********************%n"); int i=3,j=7; int min = st.query(A,M, i,j); System.out.format("RMQ for A[%d..%d]: A[%d]=%d", i,j,min,A[min]); System.out.format("%n***********************%n"); j=3;i=7; min = st.query(A,M, i,j); System.out.format("RMQ for A[%d..%d]: A[%d]=%d", i,j,min,A[min]); System.out.format("%n***********************%n"); for(int x=0;x<A.length;x++){ for(int y=0;y<x;y++){ System.out.format(" "); } for(int y=x;y<A.length;y++){ int p = st.query(A,M,x,y); System.out.format(" %d/%d",A[p],p); } System.out.println(); } } }
参考资料:
The LCA Problem Revisited (2000) by Michael A. Bender & Martin Farach-Colton