倍增總結

倍增

ST表

預處理

\(f[i][j]\)表示從\(i\)開始的長度爲\(2^{j}\)的區間(即區間\([i, i+2^{j}-1]\)算法

遞推公式(j在外層遞增):優化

\(f[i][j]=max\{f[i][j-1], f[i+2^{j-1}][j-1]\}\)spa

即將區間\([l, r]\)分爲兩個區間合併code

查詢

分爲兩段,第一段爲區間\([l, 2^k]\),第二段爲區間\([r-2^k+1, r]\),其中\(k\)爲知足\(2^{k}\le r-l+1\)的全部數中最大的那個數get

即區間\([l,r]\)的最大值爲\(max\{f[l][k], f[r-2^{k}+1][k]\}\)io

例子

忠誠 洛谷 P1816class

屢次查詢區間最小值效率

#include <cstdio>
#define MAXN 100010
#define MIN(A,B) ((A)<(B)?(A):(B))
using namespace std;
int f[MAXN][20];
int getk(int x){
    int cnt=0,cur=1;
    while(cur<=x){
        ++cnt;
        cur*=2;
    }
    return cnt-1;
}
int main()
{
    int m,n;
    scanf("%d %d", &m, &n);
    for(register int i=1;i<=m;++i)
        scanf("%d", &f[i][0]);
    int mx_len = getk(m);
    for(register int len=1;len<=mx_len;++len)
        for(register int i=1;i+(1<<len)-1<=m;++i)
            f[i][len]=MIN(f[i][len-1], f[i+(1<<(len-1))][len-1]);
    while(n--){
        int l, r;
        scanf("%d %d", &l, &r);
        int k=getk(r-l+1);
        printf("%d ", MIN(f[l][k], f[r-(1<<k)+1][k]));
    }
    return 0;
}

樹上倍增

求解LCA

預處理

首先結合dfs預處理出\(f[i][j]\)\(f[i][j]\)表示節點\(i\)向上跳\(2^{j}\)層的節點二進制

遞推公式:方法

\(f[i][j]=f[f[i][j-1]][j-1]\)

即節點\(i\)分兩次向上跳,每次跳\(2^{j-1}\)層跳到的節點就是節點\(i\)向上跳\(2^{j}\)層的節點(\(2^{j-1}\times 2=2^{j}\)

void load(int x, int fa){
    f[x][0]=fa;
    dep[x]=dep[fa]+1;
    for(int i=1;i<20;++i)
        f[x][i]=f[f[x][i-1]][i-1];
}
void dfs(int u, int fa){
    load(u, fa);
    for(int i=head[u];i;i=nxt[i]){
        int v=vv[i];
        if(v==fa) continue;
        dfs(v, u);
    }
}

同時也能夠像下面預處理出\(log^{n}_{2}\)的全部值以優化常數

for(int i=2;i<=tot;++i)
    lg2[i]=lg2[i>>1]+1; // 預處理log2n

查詢

首先使兩個查詢節點跳至同一高度後(由於它們的最近公共祖先不可能低於這兩點,跳躍方法同下),當前層記爲\(x\),而後從\(log^{x}_{2}\)到0枚舉(遞減能保證能夠徹底分解成二進制)\(j\),若是上跳\(2^{j}\)層後不重合,那麼就繼續跳,重合則不跳,使兩點層數一直逼近最近公共祖先,最後跳完\(2^{0}\)層後,兩點一定會停在最近公共祖先的下一層,因此最後直接取當前層\(i\)\(f[i][0]\)就行了。

其中,能夠直接從最大可能的\(i\)開始枚舉,由於反正\(i\)也很小。

inline int lca(int a, int b){
    if(dep[a]<dep[b]) swap(a, b);
    for(int i=20;i>=0;--i)
        if(dep[f[a][i]]>=dep[b])
            a=f[a][i];
    if(a==b) return a;
    for(int i=20;i>=0;--i)
        if(f[a][i]!=f[b][i])
            a=f[a][i], b=f[b][i];
    return f[a][0];
}

這是一種在線求\(LCA\)的算法,其實還有\(Tarjan\)這種效率高的離線算法。

\(O(1)\)求LCA

另外還有一種\(nlogn\)預處理,每次\(O(1)\)查詢的方法。

即用歐拉序+RMQ可實現\(O(1)\)求得LCA。先dfs全樹,記錄歐拉序(就是記下\(dfs\)走過的節點),而後在歐拉序上以節點深度做爲權值建ST表,每次查歐拉序上深度最小的點即爲LCA。此時將問題轉換爲區間求最小值RMQ問題。

預處理

void dfs(int u, int fa){
    dfn[u]=++tot;
    f[tot][0]=u;
    dep[u]=dep[fa]+1;
    for(int i=head[u];i;i=nxt[i]){
        int v=vv[i];
        if(v==fa) continue;
        dfs(v, u);
        f[++tot][0]=u; // 再記錄
    }
}
for(int i=1;i<20;++i)
    for(int j=1;j+(1<<i)-1<=tot;++j)
        if(dep[f[j][i-1]]<dep[f[j+(1<<(i-1))][i-1]])
            f[j][i]=f[j][i-1];
        else
            f[j][i]=f[j+(1<<(i-1))][i-1];
for(int i=2;i<=tot;++i)
    lg2[i]=lg2[i>>1]+1; // 預處理log2n

查詢

int lca(int a, int b){
    int l=dfn[a],r=dfn[b];
    if(l>r) swap(l,r);
    int lg=lg2[r-l+1];
    if(dep[f[l][lg]]<dep[f[r-(1<<lg)+1][lg]])
        return f[l][lg];
    else return f[r-(1<<lg)+1][lg];
}

求路徑上最值

求樹上兩點\(a,b\)路徑上的最小權值

咱們再設一個\(g[i][j]\)表示節點\(i\)向上跳\(2^j\)內所通過的最小權值便可,轉移方程:

\(g[i][j]=min(g[i][j-1], g[f[i][j-1]][j-1])\)

相關文章
相關標籤/搜索