ST表 與 RMQ問題 筆記

RMQ refers to Range Minimum Question

ST表是一種數據結構,專門解決RMQ問題

目录

What is RMQ ?

What is ST表 ?

 整體代碼實現


What is RMQ ?

從字面意義上理解RMQ有點粗糙,因為這裡要跟ST做連結,所以我們給他來點定義。

定義一: 符合結合律

定義二:允許區間重疊不影響結果

經典列子:

1. max([left:right])=max([left : b],[a:right])\;,where\;b >=a

2. gcd(a,b,c)=gcd(gcd(a,b),c)=gcd(a,gcd(b,c))=gcd(gcd(a,b),gcd(a,c))

3. a & b & c = a & c & b = a & (b & c)

反例:

sum([left:right])\neq sum([left:b])+sum([a:right]),where \;a< b

對於求和區間一旦重合就會影響答案

一個非常理論的算法,就是利用RMQ求出最近公共祖先。

但我不會。。。

What is ST表 ?

ST表你可以把他當成是查詢RMQ為O(1)的算法。

我們先不要理什麼ST表什麼的。

如果要讓你設計一個算法要O(1)的時間複雜度查詢,你會怎麽做?

最簡單,我們開個表,把所有區間的可能性和答案都存下來。

for(int i = 1; i <= n; i++)f[i][1]=a[i]; 
for(int i = 1; i <= n; i++){
    for(int j = 2; j <= n; j++){    
        f[i][j] = min(f[i][j - 1], a[j]);
    }
}

時間複雜度:O( n^2)

空間複雜度:O(n^2)

查詢複雜度:O(1)

但你可以發現上面的表,其實有很多浪費時間和空間的地方,因為上面的表即便在區間重疊後,還是去算一遍重疊區間的最大或最小值,這其實沒有必要,因為根據經典列子1,可以知道我們其實沒有必要去算重疊區間。

然後利用倍增思想,只初始化2^j 的區間長度的值,查詢的時候利用一些trick把問題轉為經典列子1就行了。

所以現在我們的 f(i, j) 是指以 i 為首 i + 2^j 為結尾的區間最值。

那麼狀態怎麼轉移呢?根據定義可以知道

1. f(i,0)=a_i

2. f(i, j)=max(f(i, j-1),f(i + 2^{j-1},j-1))

對於第二條妳會問為什麼?

對於區間長度為4的最小值,必然由兩個區間長度為2的轉化而來。

對於區間長度為8的最小值,必然由兩個區間長度為4的轉化而來。如此類推。

畫個圖給你看看。

for(int i = 1; i <= n; i++){
    //因為數組從index 1 開始所以要減1
    for(int j = 1; i + (1<<j) - 1; j++){
        f[i][j] = max(f[i][j - 1], f[i + (1<<(j - 1))][j - 1]);
    } 
}

那這裡的初始化的時間和空間就變成O(logn),是指數級的下降。

那要如何查詢呢?

因為我們都是按照2的次方長度來定義區間的,所以對於要查詢的區間,我們首先要得到他的區間長度,並且對他求Log2,把right 減掉這個值再讓left 加上這個值,再去看看表內那一個區間的最值大一點就可以了。

\\let\;k=log_2(right-left + 1) \\min([left:right]) = min(f(left,k),f(right-2^k + 1,k))

代碼實現query

int query(int lft, int rght){
    int k = log2(rght-left+1);
    return min(f[lft][k], f[rght - (1<<k) + 1][k]);
}

———————————————————————————————————————————所以我們的二維數組只需要花費 n * log2(n) 的空間,

 整體代碼實現

#include <iostream>
#include <cmath>
using namespace std;
const int MAXN = 1e6 + 10;
int n, m;
int st[MAXN][21];
void init(){
    for(int j = 1; i <= 21; j++){
        for(int i = 1; i+(1<<j)-1 <= n; i++){
            f[i][j] = min(st[i][j - 1], st[i + (1<<(j - 1))][j - 1]);
        }
    }
}
int query(int lft, int rght){
    int k = log2(rght - lft + 1);
    return min(st[lft][k], st[rght-(1<<k)+1][k]);
}
int main(){
    cin >> n;
    cin >> m;
    for(int i = 1; i <= n; i++){
        cin >> st[i][0];
    }
    init();
    int m;
    while(m--){
        int lft, rght;
        cin >> lft >> rght;
        cout << query(lft, rght) << endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值