RMQ(Range Minimum Query), 구간의 최솟값 구하기 알고리즘
1. 한 쿼리당 O(sqrt(n)), 선처리 O(n)
n개의 원소들을 sqrt(n)개의 원소를 갖는 그룹으로 나누어 최솟값을 미리 저장해두면 한 쿼리당 최대 3*sqrt(n)의 시간만으로 구간의 최솟값을 구할 수 있다.
arr[11]={7,5,4,3,2,6,8,11,9,1,10}; // 0-base임에 유의
// 11개짜리 배열을 3(=sqrt(11))개 씩 나누어 전처리 해주자
int r=(int)sqrt(n);
void init(){
for(int i=0;i<n;i++){
if(i%r==0) group[i/r]=arr[i];
else group[i/r]=min(group[i/r],arr[i]);
}
}
int rmq(int a, int b){ // [a,b]구간의 rmq query
int ans=INT_MAX;
int aa=a/r;
int bb=b/r;
for(int i=a;i<r*aa;i++) ans=min(ans,arr[i]);
for(int i=aa;i<bb;i++) ans=min(ans,group[i]);
for(int i=r*bb;i<=b;i++) ans=min(ans,arr[i]);
return ans;
}
쿼리 함수에서 3개의 반복문 복잡도가 모두 O(sqrt(n)) 이하이다.
2. 한 쿼리당 O(log(n)), 선처리 O(n*log(n))
1의 아이디어와 유사하다.(parse table을 활용하자!)
memo[i][j]: j부터 j+(1«i)-1의 원소들 중 가장 작은 값을 저장(즉, j부터 2^i개의 원소들 중 최소값)
이러한 행렬을 미리 저장해 둔다면 우리는 하나의 쿼리당 log(구간의 크기)의 복잡도로 최솟값을 찾을 수 있다.
const int SZ=100005; // 배열 최대 크기
const int MXH=20; // 넉넉하게 잡았다. log(SZ)보다 적당히 크면 된다.
int arr[SZ]; // 쿼리를 시행할 data
int memo[MXH][SZ]; // memoization 배열
int n;
void init_memoization(){
for(int i=0;i<n;i++) memo[0][i]=arr[i];
for(int i=1;i<MXH;i++){
for(int j=0;j<n;j++){
if((j+(1<<(i-1)))>=n) continue;
memo[i][j]=min(memo[i-1][j],memo[i-1][j+(1<<(i-1))]);
}
}
}
int rmq(int a, int b){ //[a,b]
int ans=INT_MAX;
for(int i=MXH;i>=0;i--){
if((b-a+1)&&(1<<i)){
ans=min(ans,memo[i][a]);
a+=(1<<i);
}
}
return ans;
}