题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1174
区间最大值查询,挺简单的,原来学习了线段树,很容易可以解决这个问题,不过看到
别人用ST(Sparse Table)算法来解决这个问题,这个没学过,比较好奇就看了一下。
过程也挺简单的,就是动态规划的思想。有一个dp数组。
dp[i][j] 维护的是 【i , i + 2^j - 1】这个区间的最大/最小值,
则dp[i][0] 维护的是【i, i + 2^0 -1】 = 【 i , i 】的最大/最小值。
因此dp[i][0] = a[i]
而dp[i][j] = max/min【i , j】 = max/min(dp【 i , j-1】,dp【 i + (1<<(j-1)) , j-1】
其意义如下图所示:
i ~ ( i + 2^j -1 ) 中有2^j次方个数,我们可以把这整个区间分成2个拥有2^j-1次方个数的区间。
所以dp[i][j] 是由【 i , i + 2^(j-1)-1 】和 【i+2^(j-1),i+2^j-1-1】
第一个区间就是dp[i][j-1] ,第二个区间是dp[i+2^(j-1)][j-1]。
查询的时候,假如想要left,right的最大值或最小值,时间复杂度是0(1)。
我们知道left,right区间里面由right-left + 1.
而right不恰好是 i + 2^j - 1对应的数,因此这时我们求解的时候区间应该重叠。
我们找最大的k 2^k <= (right-left+1) k = ceil(log(right-left+1))
则整个区间[left,right]可由两个区间 [left,left + 2^k -1] 和 [right-2^k+1][right]并起来构成
则区间的最大最小值就是 max/min(dp[left][k],dp[right-(1<<k)+1][k])得到。
对应如下图所示:
AC代码:ST算法
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
using namespace std;
const int maxn = 10005;
int dp[maxn][32];
void init(int N)
{
for(int i = 0; i < N; i++)
{
scanf("%d",&dp[i][0]);
}
for(int j = 1; j < 32; j++)
for(int i = 0; i < N; i++)
{
if(i+(1<<(j-1))>=N) break; ///越界
dp[i][j] = max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
int query(int left,int right)
{
int k = (int)log2(right-left+1.0);
int ans = max(dp[left][k],dp[right-(1<<k)+1][k]);
return ans;
}
int main()
{
int N,Q;
while(~scanf("%d",&N))
{
init(N);
scanf("%d",&Q);
int i,j;
while(Q--)
{
scanf("%d%d",&i,&j);
printf("%d\n",query(i,j));
}
}
return 0;
}
AC代码:线段树
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#define lchild left,mid,root<<1
#define rchild mid+1,right,root<<1|1
using namespace std;
const int maxn = 10005;
int Max[maxn<<2];
void push_up(int root)
{
Max[root] = max(Max[root<<1],Max[root<<1|1]);
}
void build(int left,int right,int root)
{
if(left == right)
{
scanf("%d",&Max[root]);
return;
}
int mid = (left+right)>>1;
build(lchild);
build(rchild);
push_up(root);
}
int query(int L,int R,int left,int right,int root)
{
if(L<=left && right<=R)
return Max[root];
int mid = (left+right)>>1;
int ans = 0;
if(L <= mid) ans = max(ans,query(L,R,lchild));
if(R > mid) ans = max(ans,query(L,R,rchild));
return ans;
}
int main()
{
int N,Q;
while(~scanf("%d",&N))
{
memset(Max,0,sizeof(Max));
build(1,N,1);
scanf("%d",&Q);
int i,j;
while(Q--)
{
scanf("%d%d",&i,&j);
int ans = query(i+1,j+1,1,N,1);
printf("%d\n",ans);
}
}
return 0;
}