RMQ 學習筆記

來源:互聯網
上載者:User

轉載自:http://hi.baidu.com/diannaochina/blog/item/48dfff33c3c36840ad4b5fb5.html

一、最近公用祖先(Least Common Ancestors)

對於有根樹T的兩個結點u、v,最近公用祖先LCA(T,u,v)表示一個結點x,滿足x是u、v的祖先且x的深度儘可能大。另一種理解方式是把T理解為一個無向無環圖,而LCA(T,u,v)即u到v的最短路上深度最小的點。

這裡給出一個LCA的例子:
例一

對於T=<V,E>

V={1,2,3,4,5}

E={(1,2),(1,3),(3,4),(3,5)}

則有:

LCA(T,5,2)=1

LCA(T,3,4)=3

LCA(T,4,5)=3

二、RMQ問題(Range Minimum Query)

RMQ問題是指:對於長度為n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中

下標在[i,j]裡的最小值下標。這時一個RMQ問題的例子:

例二

對數列:5,8,1,3,6,4,9,5,7 有:

RMQ(2,4)=3

RMQ(6,9)=6

RMQ問題與LCA問題的關係緊密,可以相互轉換,相應的求解演算法也有異曲同工之妙。

下面給出LCA問題向RMQ問題的轉化方法。

對樹進行深度優先遍曆,每當“進入”或回溯到某個結點時,將這個結點的深度存入數組

E最後一位。同時記錄結點i在數組中第一次出現的位置(事實上就是進入結點i時記錄的

位置),記做R[i]。如果結點E[i]的深度記做D[i],易見,這時求LCA(T,u,v),就等價於求

E[RMQ(D,R[u],R[v])],(R[u]<R[v])。例如,對於第一節的例一,求解步驟如下:

數列E[i]為:1,2,1,3,4,3,5,3,1

R[i]為:1,2,4,5,7

D[i]為:0,1,0,1,2,1,2,1,0

於是有:

LCA(T,5,2) = E[RMQ(D,R[2],R[5])] = E[RMQ(D,2,7)] = E[3] = 1

LCA(T,3,4) = E[RMQ(D,R[3],R[4])] = E[RMQ(D,4,5)] = E[4] = 3

LCA(T,4,5) = E[RMQ(D,R[4],R[5])] = E[RMQ(D,5,7)] = E[6] = 3

易知,轉化後得到的數列長度為樹的結點數的兩倍加一,所以轉化後的RMQ問題與LCA

問題的規模同次。

LCA轉化為RMQ問題的實現:

int D[N], E[N], R[N];class RMQ {public:    int Min[N][20];    void build(int n) {        int i, j, m;        for(i = 1; i <= n; ++i) Min[i][0] = i;        m = int(log(double(n))/log(2.0));        FOR(j, 1, m) {            FOR(i, 1, n - (1<<j) + 1) {                if(D[Min[i][j-1]] < D[Min[i+(1<<(j-1))][j-1]]) {                    Min[i][j] = Min[i][j-1];                } else {                    Min[i][j] = Min[i+(1<<(j-1))][j-1];                }            }        }    }    int query(int s, int e) {        int k = log(double(e - s + 1))/log(2.0);        if(D[Min[s][k]] < D[Min[e-(1<<k) + 1][k]])   return  Min[s][k];        else    return Min[e-(1<<k) + 1][k];    }} Q;vector<int> g[N];int in[N], cnt;bool vis[N];void init(int n) {    CL(in, 0); CL(D, 0);    CL(E, 0); CL(R, 0);    CL(vis, 0); cnt = 1;    for(int i = 1; i <= n; ++i) {        g[i].clear();    }}void dfs(int t, int dep) {    if(!vis[t]) {        R[t] = cnt;        vis[t] = true;    }    D[cnt] = dep;    E[cnt++] = t;    for(int i = 0; i < int(g[t].size()); ++i) {        dfs(g[t][i], dep + 1);        D[cnt] = dep;        E[cnt++] = t;    }}int main() {    //freopen("data.in", "r", stdin);    int t, i, n, x, y;    scanf("%d", &t);    while(t--) {        scanf("%d", &n);        init(n);        REP(i, n - 1) {            scanf("%d%d", &x, &y);            g[x].push_back(y);            in[y]++;        }        FOR(i, 1, n) {            if(in[i] == 0)  {                dfs(i, 0);                break;            }        }        cnt--;        Q.build(cnt);        scanf("%d%d", &x, &y);        x = R[x], y = R[y];        if(x > y)   swap(x, y);        printf("%d\n", E[Q.query(x, y)]);    }    return 0;}

 

RMQ問題以及ST演算法
        RMQ(Range Minimum/Maximum Query)問題是求區間最值問題。你當然可以寫個O(n)的(怎麼寫都可以吧=_=),但是萬一要詢問最值1000000遍,估計你就要掛了。這時候你可以放心地寫一個線段樹(前提是不寫錯)O(logn)的複雜度應該不會掛。但是,這裡有更牛的演算法,就是ST演算法,它可以做到O(nlogn)的預先處理,O(1)!!!地回答每個詢問。
        來看一下ST演算法是怎麼實現的(以最大值為例):
       
        首先是預先處理,用一個DP解決。設a[i]是要求區間最值的數列,f[i,j]表示從第i個數起連續2^j個數中的最大值。例如數列3 2 4 5 6 8 1 2 9 7 ,f[1,0]表示第1個數起,長度為2^0=1的最大值,其實就是3這個數。f[1,2]=5,f[1,3]=8,f[2,0]=2,f[2,1]=4……從這裡可以看出f[i,0]其實就等於a[i]。這樣,Dp的狀態、初值都已經有了,剩下的就是狀態轉移方程。我們把f[i,j]平均分成兩段(因為f[i,j]一定是偶數個數字),從i到i+2^(j-1)-1為一段,i+2^(j-1)到i+2^j-1為一段(長度都為2^(j-1))。用上例說明,當i=1,j=3時就是3,2,4,5 和 6,8,1,2這兩段。f[i,j]就是這兩段的最大值中的最大值。於是我們得到了動規方程F[i,j]=max(F[i,j-1],F[i+2^(j-i),j-1]).
     
接下來是得出最值,也許你想不到計算出f[i,j]有什麼用處,一般毛想想計算max還是要O(logn),甚至O(n)。但有一個很好的辦法,做到了O(1)。還是分開來。如在上例中我們要求區間[2,8]的最大值,就要把它分成[2,5]和[5,8]兩個區間,因為這兩個區間的最大值我們可以直接由f[2,2]和f[5,2]得到。擴充到一般情況,就是把區間[l,r]分成兩個長度為2^n的區間(保證有f[i,j]對應)。直接給出運算式:
k:=trunc(l(r-l+1)/ln(2));
ans:=max(F[l,k],F[r-2^k+1,k]);

 

我的RMQ模板 POJ 3264:

#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <stack>#include <cmath>#include <algorithm>#define CL(arr, val)    memset(arr, val, sizeof(arr))#define REP(i, n)       for(i = 0; i < n; ++i)#define FOR(i, l, h)    for(i = l; i <= h; ++i)#define FORD(i, h, l)   for(i = h; i >= l; --i)#define L(x)    x << 1#define R(x)    x << 1 | 1#define MID(l, r)  (l + r) >> 1typedef long long LL;using namespace std;const int N = 50010;int Max[N][20];int Min[N][20];int num[N], n;void init() {    int i, j, m;    FOR(i, 1, n) {        Max[i][0] = num[i];        Min[i][0] = num[i];    }    m = int(log(double(n))/log(2.0));    FOR(j, 1, m){        FOR(i, 1, n - (1<<j) + 1) {            Max[i][j] = max(Max[i][j-1], Max[i+ (1<<(j-1))][j-1]);            Min[i][j] = min(Min[i][j-1], Min[i+ (1<<(j-1))][j-1]);        }    }}int solve(int s, int e) {    int k = log(double(e - s + 1))/log(2.0);    int x = max(Max[s][k], Max[e - (1<<k) + 1][k]);    int y = min(Min[s][k], Min[e - (1<<k) + 1][k]);    return x - y;}int main() {    //freopen("data.in", "r", stdin);    int q, i, a, b;    CL(Max, 0);    CL(Min, 0);    scanf("%d%d", &n, &q);    FOR(i, 1, n)    scanf("%d", num + i);    init();    while(q--) {        scanf("%d%d", &a, &b);        printf("%d\n", solve(a, b));    }    return 0;}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.