RMQ_第一彈_Sparse Table
title: RMQ_第一彈_Sparse Table
date: 2018-09-21 21:33:45
tags:
- acm
- RMQ
- ST
- dp
- 資料結構
-
演算法
categories: -
ACM
概述
RMQ (Range Minimum/Maximum Query)
從英文便可以看出這個演算法的主要是詢問一個區間內的最值問題,,,
暑假集訓的時候學習了線段樹 ,,,
也可以對給定陣列查詢任意區間的最值問題,,,,
這兩個主要的區別就是 線段樹 可以進行單點的修改操作,,,而Sparse Table 演算法不能進行點修改,,
或者說這樣修改一次重預處理一次不划算,,,
所以說,,要是題目只是單純的多次查詢任意區間的最值,,,Sparse Table 首選,,畢竟,,畢竟寫起來比線段樹簡單得多了,,,
預處理
演算法原理
基本思想是dp,,,,
dp的狀態: 對於陣列\(a[1-n]\) ,\(F[i , j]\) 表示從第\(i\) 個位置開始 ,長度 為\(2^j\) 個數這個區間中的最值,,,;
dp的初始值:\(F[i , 0] = a[i]\) ;
狀態轉移方程:\(F[i , j] = max (F[i , j - 1] , F[i + 2^{j - 1} , j - 1])\) ;
思想:\(F[i , j]\) 就是不斷取他的左右這兩段的最值,,這兩段的長度相等,都為\(2^{j - 1}\) 個元素,,
實現
const int maxn = 5e4 + 10; int n , q; int a[maxn]; int mx[maxn][20]; int mi[maxn][20]; void rmq() { for (int i = 1; i <= n; ++i) mx[i][0] = mi[i][0] = a[i]; for (int j = 1; (1 << j) <= n; ++j) { for (int i = 1; i + (1 << j) - 1 <= n; ++i) { mx[i][j] = max(mx[i][j - 1] , mx[i + (1 << (j - 1))][j - 1]); mi[i][j] = min(mi[i][j - 1] , mi[i + (1 << (j - 1))][j - 1]); } } }
這裡我們需要注意的是迴圈的順序,我們發現外層是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]),所以這樣的方法肯定是錯誤的。
ofollow,noindex" target="_blank">本段來自某大佬部落格
查詢
思想
假如我們需要查詢的區間為(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 = log_2(8 - 2 + 1)= 2\) ,即求\(max(F[2, 2],F[8 - 2 ^ 2 + 1, 2]) = max(F[2, 2],F[5, 2])\) ;
實現
int ans(int l , int r) { int k = 0; int len = r - l + 1; while ((1 << (k + 1)) <= len) ++k; return max (mx[l][k] , mx[r - (1 << k) + 1][k]) - min (mi[l][k] , mi[r - (1 << k) + 1][k]); }
實戰
題目大意: 給定的數列a[1 - n] , 求出[l , r]這個區間內的極差 , 即最大值與最小值的差
直接套板子,,,,
ac程式碼:
#include <iostream> #include <cmath> #include <cstring> #include <cstdio> using namespace std; const int maxn = 5e4 + 10; int n , q; int a[maxn]; int mx[maxn][20]; int mi[maxn][20]; void rmq() { for (int i = 1; i <= n; ++i) mx[i][0] = mi[i][0] = a[i]; for (int j = 1; (1 << j) <= n; ++j) { for (int i = 1; i + (1 << j) - 1 <= n; ++i) { mx[i][j] = max(mx[i][j - 1] , mx[i + (1 << (j - 1))][j - 1]); mi[i][j] = min(mi[i][j - 1] , mi[i + (1 << (j - 1))][j - 1]); } } } int ans(int l , int r) { int k = 0; int len = r - l + 1; while ((1 << (k + 1)) <= len) ++k; return max (mx[l][k] , mx[r - (1 << k) + 1][k]) - min (mi[l][k] , mi[r - (1 << k) + 1][k]); } using namespace std; int main(){ while (scanf("%d%d" , &n , &q) != EOF) { for (int i = 1; i <= n; ++i) scanf("%d" , &a[i]); rmq(); while (q--) { int l , r; scanf("%d%d" , &l , &r); printf("%d\n" , ans(l , r)); } } return 0; }
kuangbin的板子:
一維:
const int MAXN = 50010; int dp[MAXN][20]; int mm[MAXN]; //初始化 RMQ, b 陣列下標從 1 開始,從 0 開始簡單修改 void initRMQ(int n,int b[]) { mm[0] = −1; for(int i = 1; i <= n; i++) { mm[i] = ((i&(i−1)) == 0)?mm[i−1]+1:mm[i−1]; dp[i][0] = b[i]; } for(int j = 1; j <= mm[n]; j++) for(int i = 1; i + (1<<j) −1 <= n; i++) dp[i][j] = max(dp[i][j−1],dp[i+(1<<(j−1))][j−1]); } //查詢最大值 int rmq(int x,int y) { int k = mm[y−x+1]; return max(dp[x][k],dp[y−(1<<k)+1][k]); }