zoukankan      html  css  js  c++  java
  • 倍增总结

    倍增

    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]})

    即将区间([l, r])分为两个区间合并

    查询

    分为两段,第一段为区间([l, 2^k]),第二段为区间([r-2^k+1, r]),其中(k)为满足(2^{k}le r-l+1)的所有数中最大的那个数

    即区间([l,r])的最大值为(max{f[l][k], f[r-2^{k}+1][k]})

    例子

    忠诚 洛谷 P1816

    多次查询区间最小值

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    int m,n;
    #define MAXN 100001
    #define LOG 18
    int st[MAXN][LOG];
    int lg2[MAXN];
    int main(){
        scanf("%d %d", &n, &m);
        memset(st, 0x3f, sizeof st);
        for(int i=1;i<=n;++i) scanf("%d", &st[i][0]);
        for(int len=1;len<LOG;++len)
            for(int i=1;i+(1<<len)-1<=n;++i)
                st[i][len]=min(st[i][len-1], st[i+(1<<(len-1))][len-1]);
        for(int i=2;i<=n;++i) lg2[i]=lg2[i>>1]+1;
        while(m--){
            int l,r;scanf("%d %d", &l, &r);
            int sz=lg2[r-l+1];
            printf("%d ", min(st[l][sz], st[r+1-(1<<sz)][sz]));
        }
        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} imes 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]))

  • 相关阅读:
    LeetCode:Remove Nth Node From End of List
    LeetCode:Swap Nodes in Pairs
    LeetCode:Merge Two Sorted Lists
    LeetCode:Maximum Subarray
    LeetCode:Linked List Cycle
    LeetCode:Search Insert Position
    LeetCode:Roman to Integer
    算法程序设计题语言类笔记
    我的小游戏之2048
    LeetCode:Best Time to Buy and Sell Stock II
  • 原文地址:https://www.cnblogs.com/santiego/p/9737892.html
Copyright © 2011-2022 走看看