链接:http://poj.org/problem?id=3264
RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
动态规划:
设data[i]是要求最大值的数列。dp[i][j]表示以第i个数开始,长度为2^j段的最大值。 若将该段从中间分开(因为是2^j,肯定能分成两段),得到的子序列为 i~2^(j-1)-1 和 i+2^(j-1)~i+2^j-1。。写成方程的形式,即为:
dp[i][j]=strcat( dp[i][j-1] , dp[i+2^[j-1]][j-1] ) //strcat()为字符串连接函数
So....状态转移方程为:dp[i][j]=MAX( dp[i][j-1] , dp[i+2^[j-1]][j-1] ) ;
dp[i][0]为它本身
以下摘自:http://blog.csdn.net/niushuai666/article/details/7401403
代码为:
void RMQ(int num) //预处理->O(nlogn) { for(int j = 1; j < 20; ++j) for(int i = 1; i <= num; ++i) if(i + (1 << j) - 1 <= num) { maxsum[i][j] = max(maxsum[i][j - 1], maxsum[i + (1 << (j - 1))][j - 1]); minsum[i][j] = min(minsum[i][j - 1], minsum[i + (1 << (j - 1))][j - 1]); } }
,其中1<<j为2^j
这里我们需要注意的是循环的顺序,我们发现外层是j,内层所i,这是为什么呢?可以是i在外,j在内吗?
答案是不可以。因为我们需要理解这个状态转移方程的意义。
状态转移方程的含义是:先更新所有长度为F[i,0]即1个元素,然后通过2个1个元素的最值,获得所有长度为F[i,1]即2个元素的最值,然后再通过2个2个元素的最值,获得所有长度为F[i,2]即4个元素的最值,以此类推更新所有长度的最值。
而如果是i在外,j在内的话,我们更新的顺序就是F[1,0],F[1,1],F[1,2],F[1,3],表示更新从1开始1个元素,2个元素,4个元素,8个元素(A[0],A[1],....A[7])的最值,这里F[1,3] = max(max(A[0],A[1],A[2],A[3]),max(A[4],A[5],A[6],A[7]))的值,但是我们根本没有计算max(A[0],A[1],A[2],A[3])和max(A[4],A[5],A[6],A[7]),所以这样的方法肯定是错误的。
为了避免这样的错误,一定要好好理解这个状态转移方程所代表的含义。
(二)然后是查询。
假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可以重复,比如查询5,6,7,8,9,我们可以查询5678和6789)。
因为这个区间的长度为j - i + 1,所以我们可以取k=log2( j - i + 1),则有:RMQ(A, i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。
举例说明,要求区间[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2],F[8 - 2 ^ 2 + 1, 2]) = max(F[2, 2],F[5, 2]);
在这里我们也需要注意一个地方,就是<<运算符和+-运算符的优先级。
比如这个表达式:5 - 1 << 2是多少?
答案是:4 * 2 * 2 = 16。所以我们要写成5 - (1 << 2)才是5-1 * 2 * 2 = 1。
代码:
#include<iostream> #include<cstdio> using namespace std; const int N = 50005; int dpmax[N][20], dpmin[N][20]; void RMQ(int n) { for(int j = 1; j != 20; j++) for(int i = 1; i <= n; i++) if(i + (1 << j) - 1 <= n) //i+2^j { dpmax[i][j] = max(dpmax[i][j - 1], dpmax[i + (1 << (j - 1))][j - 1]); // i+2^(j-1) dpmin[i][j] = min(dpmin[i][j - 1], dpmin[i + (1 << (j - 1))][j - 1]); } } int main() { int num, query; int a, b; while(scanf("%d %d", &num, &query) != EOF) { for(int i = 1; i <= num; ++i) { scanf("%d", &dpmax[i][0]); dpmin[i][0] = dpmax[i][0]; } RMQ(num); while(query--) { scanf("%d%d", &a, &b); int k = (int)(log(b - a + 1.0) / log(2.0)); //log2(b-a+1)....换底公式 int maxsum = max(dpmax[a][k], dpmax[b - (1 << k) + 1][k]); int minsum = min(dpmin[a][k], dpmin[b - (1 << k) + 1][k]); printf("%d\n", maxsum - minsum); } } return 0; }