1. 程式人生 > >RMQ問題 及st表

RMQ問題 及st表

rmq問題是多次詢問閉區間內的最值,時間複雜度達到O(nlogn+m)

st表是一種不支援線上修改演算法,專門解決rmq

下面是st表的詳情

 

st[i][j]表示在i - ((2^j)-1)這個區間之內的最值

st[i][0]就等於a[i],不用說了

 

既然這樣,那麼怎麼求出st的其他值呢?

st表O(nlogn)的預處理其實像極了LCA的初始化

因此st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);

怎麼樣,是不是極為相似

 

那如果要查詢怎麼辦呢,要查詢的區間可不滿足剛好是2的若干次方哦?

大家應該都知道,st表的單次查詢時間是O(1),所以我們要用常數的查詢方法(這不是廢話嗎,其實這就是廢話

先設t為log(y-x+1),也就是2^t<=(y-x+1) && 2^(t+1)>(y-x+1)

那麼就可用min(st[x][t],st[y-(1<<t)-1][t])完美的覆蓋了這個區間了

 

因此st表不支援線上修改,很明顯,這個演算法難以修改,如果要修改最好用線段樹

 

給一個例題吧

洛谷P1816 忠誠

題目描述

老管家是一個聰明能幹的人。他為財主工作了整整10年,財主為了讓自已賬目更加清楚。要求管家每天記k次賬,由於管家聰明能幹,因而管家總是讓財主十分滿意。但是由於一些人的挑撥,財主還是對管家產生了懷疑。於是他決定用一種特別的方法來判斷管家的忠誠,他把每次的賬目按1,2,3…編號,然後不定時的問管家問題,問題是這樣的:在a到b號賬中最少的一筆是多少?為了讓管家沒時間作假他總是一次問多個問題。

輸入輸出格式

輸入格式:

 

輸入中第一行有兩個數m,n表示有m(m<=100000)筆賬,n表示有n個問題,n<=100000。

第二行為m個數,分別是賬目的錢數

後面n行分別是n個問題,每行有2個數字說明開始結束的賬目編號。

 

輸出格式:

 

輸出檔案中為每個問題的答案。具體檢視樣例。

 

輸入輸出樣例

輸入

10 3
1 2 3 4 5 6 7 8 9 10
2 7
3 9
1 10

輸出

2 3 1

題解:

閉區間多次求最值(rmq)

程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<queue>
#include<stack>
#include<cmath>
#include<map>
using namespace std;
inline int read(){
    int x=0,f=0;char s=getchar();
    while(!isdigit(s))f|=s=='-',s=getchar();
    while( isdigit(s))x=(x<<1)+(x<<3)+s-48,s=getchar();
    return !f?x:-x;
}
const int N=1e6+10;
const int logn=20;
int n,k;
int Log[N],st[N][logn+5],a[N];
int main(){
	int x,y,t;
	n=read();k=read();
	for(int i=1;i<=n;i++)a[i]=read();
	Log[0]=-1;//log[i]的意義:2^log[i]<=i && 2^(log[i]+1)>i 
	memset(st,63,sizeof(st));//初始化無窮大 
	for(int i=1;i<=n;i++)st[i][0]=a[i],Log[i]=Log[i>>1]+1;//初始化log和st[i][0] 
	for(int j=1;j<=logn;j++)//nlogn預處理出每一項st 
		for(int i=1;i+(1<<j)-1<=n;i++)
			st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
	while(k--){//查詢 
		x=read(),y=read();
		t=Log[y-x+1];
		printf("%d ",min(st[x][t],st[y-(1<<t)+1][t]));
	}
	return 0;
}

以上就是rmq演算法,我覺得畫圖似乎沒有什麼必要,謝謝大家的支援