RMQ(Range Minimum/Maximum Query),即区间最值查询。
是指这样一个问题:对于长度为n的数列x,回答若干询问RMQ(A,i,j)(i,j<=n),返回列x下标在[i,j]之间的最小/大值。
x[i]是要求区间最值的数列,dp[i, j]表示从第[x[i] , x[i]+2^j) 左闭右开数中的最大值。
dp[1,0]表示第1个数起,长度为2^0=1的最大值。dp[i,0]就等于x[i]。(dp的初始值)
这样,dp的状态、初值都已经有了,剩下的就是状态转移方程。
我们把x[i,j]平均分成两段(因为x[i,j]一定是偶数个数字),
从 [i , i + 2 ^ (j - 1) )为一段,
[i + 2 ^ (j - 1) , i + 2 ^ (j - 1))为一段(长度都为2 ^ (j - 1))。
dp[i,j]就是这两段各自最大值中的最大值。
于是我们得到了状态转移方程dp[i, j]=max(dp[i,j-1], dp[i + 2^(j-1),j-1])。
const int Max_N = 100008 ; int dp[Max_N][20] ; int x[Max_N] ; int n ; void RQM_INIT(){ for(int i = 1 ; i <= n ; i++) dp[i][0] = x[i] ; for(int j = 1 ; (1<<j) <= n ; j++) for(int i=1 ; i+(1<<(j-1)) <= n ; i++) dp[i][j] = min(dp[i][j-1] , dp[i+(1<<(j-1))][j-1]) ; } int RQM(int L , int R){ int k = 0 ; while((1<<(k+1)) <= R-L+1) k++ ; return min(dp[L][k] , dp[R-(1<<k)+1][k]) ; }
南将军统率着N个士兵,士兵分别编号为1~N,南将军经常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进行比较,计算出两个人的杀敌数差值,用这种方法一方面能鼓舞杀敌数高的人,另一方面也算是批评杀敌数低的人,起到了很好的效果。
所以,南将军经常问军师小工第i号士兵到第j号士兵中,杀敌数最高的人与杀敌数最低的人之间军功差值是多少。
现在,请你写一个程序,帮小工回答南将军每次的询问吧。
注意,南将军可能询问很多次。
5 2 1 2 6 9 3 1 2 2 4
1 7
const int Max_N = 100008 ; int dp[Max_N][20][2] ; #define tmin 0 #define tmax 1 int n , x[Max_N] ; void RMQ_INIT(){ for(int i = 1 ; i <= n ; i++) dp[i][0][tmin] = dp[i][0][tmax] = x[i] ; for(int j = 1 ; (1<<j) <= n ; j++) for(int i = 1 ; i+(1<<(j-1)) <= n ; i++){ dp[i][j][tmin] = min(dp[i][j-1][tmin] , dp[i+(1<<(j-1))][j-1][tmin]) ; dp[i][j][tmax] = max(dp[i][j-1][tmax] , dp[i+(1<<(j-1))][j-1][tmax]) ; } } int RMQ(int L , int R){ int k = 0 ; while((1<<(k+1)) <= R-L+1) k++ ; return max(dp[L][k][tmax] , dp[R-(1<<k)+1][k][tmax]) - min(dp[L][k][tmin] , dp[R-(1<<k)+1][k][tmin]) ; } int main(){ int t , i , l , r ; while(scanf("%d%d" , &n , &t) != EOF){ for(i = 1 ; i <= n ; i++) scanf("%d" ,&x[i]) ; RMQ_INIT() ; while(t--){ scanf("%d%d" ,&l , &r) ; if(l > r) swap(l , r) ; printf("%d\n" , RMQ(l , r)) ; } } return 0 ; }
题目1544:数字序列区间最小值
给定一个数字序列,查询任意给定区间内数字的最小值。
输入包含多组测试用例,每组测试用例的开头为一个整数n(1<=n<=100000),代表数字序列的长度。
接下去一行给出n个数字,代表数字序列。数字在int范围内。
下一行为一个整数t(1<=t<=10000),代表查询的次数。
最后t行,每行给出一个查询,由两个整数表示l、r(1<=l<=r<=n)。
对于每个查询,输出区间[l,r]内的最小值。
5 3 2 1 4 3 3 1 3 2 4 4 5
1 1 3
using namespace std ;
typedef long long LL ;
const int N_size=100008 ;
int N ,M ;
struct Seg{
int left ;
int right ;
int min_num ;
}seg[N_size*3]; //二叉树性质,注意这个地方
int num[N_size] ;
void make_tree(int L ,int R ,int id){
seg[id].left=L ;
seg[id].right=R ;
if(L==R){
seg[id].min_num=num[L] ;
return ;
}
int mid=(L+R)>>1 ;
make_tree(L,mid,id*2) ;
make_tree(mid+1,R,id*2+1) ;
seg[id].min_num=Min(seg[id*2].min_num,seg[id*2+1].min_num) ;
}
int query(int L ,int R ,int id){
if(L<=seg[id].left&&seg[id].right<=R)
return seg[id].min_num ;
int mid=(seg[id].left+seg[id].right)>>1 ;
if(R<=mid)
return query(L,R,id*2) ;
else if(mid<L)
return query(L,R,id*2+1) ;
else
return Min(query(L,mid,id*2),query(mid+1,R,id*2+1)) ;
}
int main(){
int L ,R ;
while(scanf("%d",&N)!=EOF){
for(int i=1;i<=N;i++)
scanf("%d",&num[i]) ;
make_tree(1,N,1) ;
scanf("%d",&M) ;
while(M--){
scanf("%d%d",&L,&R) ;
if(L>R)
swap(L,R) ;
printf("%d\n",query(L,R,1)) ;
}
}
return 0 ;
}
区间最值,线段树入门最基本的试题。