1. 程式人生 > >(沒有介紹標準演算法的)RMQ問題

(沒有介紹標準演算法的)RMQ問題

感謝杜哥程式碼滋磁

RMQ (Range Minimum/Maximum Query)問題是指:對於長度為n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j裡的最小(大)值,也就是說,RMQ問題是指求區間最值的問題。

主要方法及複雜度如下: 1、樸素(即搜尋),O(n)-O(qn) online。 2、線段樹,O(n)-O(qlogn) online。 3、ST(實質是動態規劃),O(nlogn)-O(q) online。 ST演算法(Sparse Table),以求最大值為例,設d[i,j]表示[i,i+2^j-1]這個區間內的最大值,那麼在詢問到[a,b]區間的最大值時答案就是max(d[a,k], d[b-2^k+1,k]),其中k是滿足2^k<=b-a+1(即長度)的最大的k,即k=[ln(b-a+1)/ln(2)]。 d的求法可以用動態規劃,d[i, j]=max(d[i, j-1],d[i+2^(j-1), j-1])。 4、RMQ標準演算法:先規約成LCA(Lowest Common Ancestor),再規約成約束RMQ,O(n)-O(q) online。 首先根據原數列,建立笛卡爾樹,從而將問題線上性時間內規約為LCA問題。LCA問題可以線上性時間內規約為約束RMQ,也就是數列中任意兩個相鄰的數的差都是+1或-1的RMQ問題。約束RMQ有O(n)-O(1)的線上解法,故整個演算法的時間複雜度為O(n)-O(1)。        ——來自百度百科 一·搜尋
我懶得寫程式碼,應該不太難就對了qwq   二·線段樹 我之前寫炸了的程式碼忘記儲存了,那麼就在這裡貼上杜哥的程式碼好了emmm 這個維護的是區間最小值(廢話)還是很好懂的qwq
#include<iostream>
#include<cstdio>
#define maxn 1000010
#define INF 11000000
using namespace std;

int n, m;

int a[maxn];

#define lc i << 1
#define rc i << 1 | 1
int T[maxn * 4];
inline void maintain(int i){T[i] = min(T[lc], T[rc]);}

void build(int i, int l, int r){
    if(l == r){T[i] = a[l]; return ;}
    int m = l + r >> 1;
    build(lc, l, m); build(rc, m + 1, r);
    maintain(i);
}

void update(int i, int l, int r, int k, int v){
    if(l == r){T[i] = v; return ;}
    int m = l + r >> 1;
    if(k <= m) update(lc, l, m, k, v);
    else update(rc, m + 1, r, k, v);
    maintain(i);
}

int query(int i, int l, int r, int L, int R){
    if(l > R || r < L) return INF;
    if(L <= l && r <= R) return T[i];
    int m = l + r >> 1;
    return min(query(lc, l, m, L, R), query(rc, m + 1, r, L, R));
}

inline void solve_1(){
    int x, y; scanf("%d%d", &x, &y);
    update(1, 1, n, x, y);
}

inline void solve_2(){
    int x, y; scanf("%d%d", &x, &y);
    printf("%d\n", query(1, 1, n, x, y));
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    build(1, 1, n);
    scanf("%d", &m);
    for(int i = 1; i <= m; ++i){
        int opt; scanf("%d", &opt);
        switch(opt){
        case 1 : solve_1(); break;
        case 0 : solve_2(); break;
        }
    }
    return 0;
}

  

三·ST表 看了百度百科才知道這竟然是動態規劃?!告辭.jpg ST表有兩維,st[i][j]表示[j,j+2^i-1]的範圍內的最大(小)值。 如何維護? 首先我們可以確定,st[0][j]就是這個數本身,所以我們可以在此基礎上進行DP。 二分的話顯然會快我們就二分好了qwq  於是 很顯然,[j,j+2^i-1]可以分成區間[j,j+2^(i-1)-1]和[j+2^(i-1),j+2^i],我們也就 輕鬆地得到了狀態轉移方程:st[i][j]=max(st[i-1][j],st[i-1][j+(1<<(i-1))])   一個小優化:提前預處理好[1,n]中每個數的log值 (為什麼最大要到20呢?可能因為2^20足夠大吧qwq)
#include<cstdio>
#include<iostream>
using namespace std;
int Log[100005],st[23][100005],n,l,r,m;

inline int max(int a,int b){
	return a>b? a:b;
}

inline long long read(){
	long long a=0; int f=0; char p=getchar();
	while(!isdigit(p)) {f|=p=='-'; p=getchar();}
	while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48); p=getchar();}
	return f? -a:a;
}

int main()
{
	n=read(),m=read();
	for(int i=2;i<=n;++i) Log[i]=Log[(i>>1)]+1;
	for(int i=1;i<=n;++i)
		st[0][i]=read();
	for(int i=1;i<=20;++i)
		for(int j=1;j+(1<<i)-1<=n;++j)
			st[i][j]=max(st[i-1][j],st[i-1][j+(1<<(i-1))]);
	while(m--){
		l=read(),r=read();
		int t=Log[r-l+1];
		printf("%d\n",max(st[t][l],st[t][r-(1<<t)+1]));
	}
	return 0;
}

  

  四·標準演算法 啥?這還有標準演算法?笛卡爾樹?不認識不認識告辭了