今天学弟在群里直播讲课,讲了RMQ,以前摸鱼太多这个题目并没看出来是啥,然后就去凑了个热闹。
RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
对于这个问题,区间最值,多次查询,我第一反应是线段树,也确实是线段树最值问题的基础模型,线段树博客其他文章中有,这里不单独拿出来了。
学弟讲这个题目,讲了3中方法暴力、动态规划,ST(Sparse Table)
暴力:没什么好说
int RMQ(int A[], int mi, int ma) { int rt_max = -INF, rt_min = INF; for(int i=mi; i<=ma; i++) { rt_max = max(A[i], rt_max); rt_min = min(A[i], rt_min); } return {...}; }
动态规划:
dp[i][j]维护为从i到j的最大值或最小值,状态转移方程即:
dp[i][j]=max(dp[i][j-1],dp[j][j]);
这样在最开始将所有的值全部进行预处理,每次查询只要查询dp[i][j]的值,就能直接得到A[i]-A[j]的最大值和最小值:
int dp[MX][MX]; void init_RMQ(int A[], int n) { for(int i = 0; i < n; i++) { dp[i][i] = A[i]; } for(int i = 0; i < n; i++) { for(int j = i + 1; j < n; j++) { dp[i][j] = max(dp[i][j-1], dp[j][j]); } } }
ST算法:
这是一种用二分优化的DP,dp[i][j]为从i开始到i+2^j-1的最值
dp[i][0] = max(A[i] to A[i + (2^j) - 1]) ...A[i] len = 2^0 = 1 dp[i][1] = max(A[i] to A[i + (2^j) - 1]) ...A[i],A[i+1]; len = 2^1 = 2 dp[i][2] = max(A[i] to A[i + (2^j) - 1]) ...A[i],A[i+1],A[i+2],A[i+3] len = 2^2 = 4
dp[i][3] = .... len = 2^3 = 8 ...
dp[i][j]的状态转移方程如下
dp[i][j] = max(dp[i][j - 1], dp[i + 2^(j-1)][j - 1]);
void init_RMQ(int A[], int n) { for(int i = 1; i <= n; i++) { dp[i][0] = A[i]; } for(int j = 1; (1<<j) <= n; j++) { for(int i = 1; (i + (1<<j) - 1) <= n; i++) { dp[i][j] = max(dp[i][j-1], dp[i + (1<<(j-1))][j-1]); } } }
当我们需要查询A[1]-A[7]的时候,查询的长度为7,我们则可以查询两个长度为4的区间来确定这个区间的最值,而log2(4) = 2
转化成dp则为只需要查询dp[1][2]和dp[7-4+1][2]的两个的最值比较即可:max(dp[1][2],dp[4][2])
int Query_max(int l,int r) { int k= log2(r - l + 1); return ( max(dp[l][k], dp[r - (1<<k) + 1][k])); }