zoukankan      html  css  js  c++  java
  • ST表

    ST表(稀疏表)

    概述

    ST表(Sparse Table,稀疏表)是一种主要用于求解可重复贡献问题的数据结构,它基于倍增思想,通过预处理做到快速在线查询,不支持修改

    其中可重复贡献问题是指对于一个元素来说,重复计算多次得到的答案不会改变的问题,例如求区间最值(RMQ),求区间最大公约数 ((gcd))等,然而区间和,区间积等问题则不属于可重复贡献问题

    过程

    预处理

    下面我们以区间最大值问题为例,介绍ST表的具体实现

    问题:给定长度为 (n) 的序列和 (q) 次询问,每次询问给出 (l,r) ,你需要给出序列 ([l,r]) 区间的最大值

    数据范围:对于 (100\%) 的数据,满足 (nin[1,10^5])(qin[1,2 imes10^5])(l,rin[1,n]),序列中的任一元素 (a_iin[1,10^9])

    点我传送 Luogu P3865 【模板】ST 表

    首先我们考虑暴力,对于每次询问都扫描 ([l,r]),时间复杂度 (Theta(nm)) 显然T得飞起

    那么该怎么解决呢?

    考虑预处理出一部分区间的 (max),在询问时只需要将覆盖区间的 (max) 叠加就可以得到答案

    那么应该预处理哪些区间呢?

    考虑利用倍增思想,每次预处理出以 (i) 为起点,往后数 (2^j) 个数的区间 (max),也就是 ([i,i+2^j-1]) 的区间 (max),不妨设 (f_{i,j}) 表示以 (i) 为起点,往后数 (2^j) 个数的区间 (max)

    怎么合并出更大的区间呢?

    可以想到把 (2^j) 拆分为两个 (2^{j-1}) 相加,这里可能有些抽象,我们画图来进行更好的理解

    image

    那么有 (f_{i,j}=max(f_{i,j-1},f_{i+2^{j-1},j-1})),类似于DP,只需要递推求解即可

    由于需要大量计算对数,每次都使用 (log) 函数十分低效,我们可以把 (log_2) 全部预处理出来,使用的时候直接调用即可,具体方法见代码

    c++ code

    #define MAXN 100005
    int n,f[MAXN][21],lg2[MAXN];//记得定义为全局变量,初始值才是0
    for(int i=2;i<=n;++i) lg2[i]=lg2[i>>1]+1;//预处理log_2
    for(int i=1;i<=n;++i) scanf("%d",&f[i][0]);//f[i][0]直接读入即可
    for(int j=1;j<=lg2[n];++j)//最外层枚举j,相当于每次都翻倍
        for(int i=1,tmp=(1<<j)-1;i+tmp<=n;++i)//内层枚举i,遍历这一层数组,从上一层更新
            f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    

    查询

    对于每一次询问区间 ([l,r]),我们要怎么做才能处理出最大值呢?

    我们先计算出满足 (2^kle r-l+1<2^{k+1})(k),也就是让 (2^k) 小于区间长度的最大的 (k),这样“从 (l) 开始的 (2^k) 个数”以及“以 (r) 结尾的 (2^k) 个数”就一定能覆盖整个询问区间 ([l,r]),也就是“从 (l) 开始的 (2^k) 个数”以及“从 (r-2^k)+1 开始的 (2^k) 个数”

    为什么呢?假设这两段无法完全覆盖 ([l,r]) 那么有 (2 imes2^k<|[l,r]|),那么 (k) 就不是让 (2^k) 小于区间长度的最大的 (k),与我们的定义矛盾

    “从 (l) 开始的 (2^k) 个数”以及“以 (r) 结尾的 (2^k) 个数”这两段区间中可能会有重叠的部分,计算时会被重复统计,这就是为什么ST表只能用于处理可重复贡献问题

    同样,再次上图帮助理解

    image

    对于每次询问,(f_{l,k})(f_{r-2^k+1,k}) 的最大值就是 ([l,r]) 的最大值

    c++ code

    int q,l,r,k;
    scanf("%d",&q);
    while(q--)//一共q次询问
    {
        scanf("%d%d",&l,&r);
        k=lg2[r-l+1];
        printf("%d
    ",max(f[l][k],f[r-(1<<k)+1][k]));
    }
    

    复杂度分析

    (注意!以下复杂度分析仅仅适用于RMQ问题,而区间最大公约数的复杂度不在此讨论)

    预处理时双层循环,外层枚举 (j),一共枚举 (log_2n) 次,内层枚举 (i),对于每一个 (j) 都要枚举 (n) 次,所以预处理总复杂度为 (Theta(nlog n))

    对于每个询问,只用求一次 (max),复杂度为 (Theta(1))

    小结

    ST表是一种主要用于求解可重复贡献问题的数据结构,尽管能维护的东西有限并且不支持修改操作,但是因为其代码简短,思想清晰,使用方便的特点,在很多地方都有应用,例如求最近公共祖先等等

    利用分块思想,笛卡尔树等其他算法或思想还可以对ST表进行优化或拓展,感兴趣的读者可以自行查阅


    该文为本人原创,转载请注明出处

    博客园传送门

    洛谷传送门

  • 相关阅读:
    UVa 12174 (滑动窗口) Shuffle
    UVa 1607 (二分) Gates
    CodeForces ZeptoLab Code Rush 2015
    HDU 1525 (博弈) Euclid's Game
    HDU 2147 (博弈) kiki's game
    UVa 11093 Just Finish it up
    UVa 10954 (Huffman 优先队列) Add All
    CodeForces Round #298 Div.2
    UVa 12627 (递归 计数 找规律) Erratic Expansion
    UVa 714 (二分) Copying Books
  • 原文地址:https://www.cnblogs.com/cmy-blog/p/ST.html
Copyright © 2011-2022 走看看