码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 算法学习笔记(3.1): ST算法


    合集 - 算法学习笔记(36)
    1.算法学习笔记(∞):杂项08-172.算法学习笔记(1): 欧几里得算法及其扩展01-123.算法学习笔记(2): 欧拉定理与逆元01-134.算法学习笔记(3): 倍增01-12
    5.算法学习笔记(3.1): ST算法10-13
    6.算法学习笔记(4): 并查集及其优化01-127.算法学习笔记(5): 最近公共祖先(LCA)01-128.算法学习笔记(6): 树链剖分01-129.算法学习笔记(7): 二分图01-1310.算法学习笔记(8): 网络流01-1311.算法学习笔记(8.0): 网络流前置知识01-1312.算法学习笔记(8.1): 网络最大流算法 EK, Dinic, ISAP01-1413.算法学习笔记(8.2): 上下界网络流01-1414.算法学习笔记(9): 中国剩余定理(CRT)以及其扩展(EXCRT)01-1515.算法学习笔记(10): BSGS算法及其扩展算法01-1616.算法学习笔记(11): 原根01-1617.算法学习笔记(12): 线性基02-0318.算法学习笔记(13): Manacher算法02-0719.算法学习笔记(14): 字符串哈希02-0620.算法学习笔记(15): Trie(字典树)02-0821.算法学习笔记(16): 组合数学基础02-0822.算法学习笔记(17): 快速傅里叶变换(FFT)02-1023.算法学习笔记(18): 平衡树(一)03-1024.算法学习笔记(19): 树上启发式合并(DSU on tree)03-1825.算法学习笔记(20): AC自动机03-2626.算法学习笔记(21): 平衡树(二)05-1327.算法学习笔记(22): 逆序对与原序列05-3128.算法学习笔记(23): 马尔可夫链中的期望问题06-0129.算法学习笔记(24): 狄利克雷卷积和莫比乌斯反演07-2730.算法学习笔记(25): 矩阵树定理06-1631.算法学习笔记(26): 计算几何07-2232.算法学习笔记(27): 后缀排序07-2633.算法学习笔记(28): 筛法07-2634.算法学习笔记(29):分块09-2035.算法学习笔记(30):Kruskal 重构树10-1336.算法学习笔记(31): 李超线段树10-20
    收起

    ST表

    在RMQ(区间最值)问题中,著名的ST算法就是倍增的产物。ST算法可以在 O(nlog⁡n)" role="presentation">O(nlogn)O(nlog⁡n) 的时间复杂度能预处理后,以 O(1)" role="presentation">O(1)O(1) 的复杂度在线回答区间 [l, r] 内的最值。

    当然,ST表不支持动态修改,如果需要动态修改,线段树是一种良好的解决方案,是 O(n)" role="presentation">O(n)O(n) 的预处理时间复杂度,但是查询需要 O(log⁡n)" role="presentation">O(logn)O(log⁡n) 的时间复杂度

    那么ST表中倍增的思想是如何体现的呢?

    一个序列的子区间明显有 n2" role="presentation">n2n2 个,根据倍增的思想,我们在这么多个子区间中选择一些长度为 2" role="presentation">22 的整数次幂的区间作为代表值。

    设 st[i][j]" role="presentation">st[i][j]st[i][j] 表示子区间 [i,i+2j)" role="presentation">[i,i+2j)[i,i+2j) 里最大的数

    也可以表示为 [i,i+2j−1]" role="presentation">[i,i+2j−1][i,i+2j−1],无论如何,其中有 2j" role="presentation">2j2j 个元素

    下文中的 a" role="presentation">aa 表示原序列

    递推边界明显是 st[i][0]=a[i]" role="presentation">st[i][0]=a[i]st[i][0]=a[i]。

    于是,根据成倍增长的长度,有了递推公式

    st[i][j]=max(st[i][j−1],st[i+2j−1][j−1])" role="presentation">st[i][j]=max(st[i][j−1],st[i+2j−1][j−1])st[i][j]=max(st[i][j−1],st[i+2j−1][j−1])

    当询问任意区间 [l,r]" role="presentation">[l,r][l,r] 的最值时,我们先计算出一个最大的 k" role="presentation">kk 满足:2k≤r−l+1" role="presentation">2k≤r−l+12k≤r−l+1,即需要不大于区间长度。那么,由于二进制划分我们可以知道,这个最大的 k 一定满足 2k+1≥r−l+1" role="presentation">2k+1≥r−l+12k+1≥r−l+1,即我们只需要将两个长度为 2k" role="presentation">2k2k 的区间合并即可。

    又根据 max(a, a) = a 可以知道,重复计算区间是没有任何问题的。

    所以,在寻找最值的时候就有了以下公式:

    max(a[l,r])=max(st[l][k],st[r−2k+1][k])" role="presentation">max(a[l,r])=max(st[l][k],st[r−2k+1][k])max(a[l,r])=max(st[l][k],st[r−2k+1][k])

    那么这里给出一种参考代码

    // 啊,写这种预处理以2位底的对数的整数值的方式
    // 我主要是为了将代码模块化,做到低耦合度
    // 完全是可以分开来写的
    class Log2Factory {
    private:
        int lg2[N];
    public:
        void init(int n) {
            for (int i = 2; i <= n; ++i) lg2[i] = lg2[i >> 1] + 1;
        }
    
        // 重载()运算符
        int operator() (const int &i) {
            return lg2[i];
        }
    };
    
    template<typename T>
    class STable {
    private:
        typedef T(*OP_FUNC)(T, T);
    
        Log2Factory Log2;
        T f[N][17]; // maybe most of the times k=17 is ok, make sure 2^k greater than N;
        OP_FUNC op;
    public:
        void setOp(OP_FUNC fc) {
            op = fc;
        }
    
        void init(T *a, int n) {
            for (int i = 1; i <= n; ++i)
                f[i][0] = *(++a);
    
            int t = Log2(n);
            // f[i][k] is the interval of [i, i + 2^k - 1]
            // so f[i][k] can equal to the op sum of [i, i^k - 1]
            // let r = i^k - 1
            // => f[r - (1^k) + 1][k] can equal to the op sum of [i][k]
            for (int k = 1; k <= t; ++k) {
                for (int i = 1; i + (1<<k) - 1 <= n; ++i)
                    f[i][k] = op(f[i][k-1], f[i + (1<<(k-1))][k-1]);
            }
        }
    
        const T query(int l, int r) {
            int k = Log2(r - l + 1);
            return op(f[l][k], f[r - (1<<k) + 1][k]);
        }
    };
    

    这……写法很神奇,注意修改!

    扩展 - 运算

    ST 算法不仅仅是可以求区间的最值的,只要是满足静态的,满足区间加法的问题大多数情况都可以通过 ST 表实现。

    那么区间加法是什么意思呢?

    定义我们需要对数列的筛选函数为 op ,则需要 op 满足以下性质

    • op(a, a) = a ,即重复参与运算不改变最终影响

    • op(a, b) = op(b, a) ,即满足交换律

    • op(a, op(b, c)) = op(op(a, b), c) ,即满足结合律

    举个例子,如果我们求区间是否有负数,可以将 op 设为如下逻辑:

    bool op(bool a, bool b) {
        return a | b;
    }
    

    相应的,初始化的方式也需要更改

    if (a[i] < 0) st[i][0] = true;
    else st[i][0] = false;
    

    再举一个例子,如果我们需要求区间是否全为偶数时,则初始化为

    if (a[i] % 2 == 0) st[i][0] = true;
    else st[i][0] = false;
    

    操作 op 定义为

    bool op(bool a, bool b) {
        return a & b;
    }
    

    由此可见,其实ST算法可以做到的不仅仅是区间最值那么普通的东西啊。

    但是,由于 加法 不满足性质一,所以,ST表通过这种方法并不能求得区间的所有满足某种性质的元素的个数。但是,通过另外一种 query 方式,我们可以做到这样。

    扩展 - 区间

    那么这个部分我们将讨论如何利用ST表做到上文例子中求区间偶数的个数。

    同样,由于我们可以通过二进制划分,所以可以将某一个区间长度转化为多个长度为2的整数幂次方的子区间,并且可以保证这些区间不相互重叠。

    所以我们可以利用这个处理 op(a, a) != a 的情况了。

    其实这是借鉴了一点线段树的思路

    虽然不如用线段树,但是胜在这常数极小 QwQ

    那么可以写出以下代码

    int query(int l, int r) {
        if (l == r) return st[l][0];
        int k = log2(r - l + 1);
        return op(st[l][k], query(l + (1<<k), r))
    }
    

    这样就满足了区间不重叠

    或许会有一个问题,为什么初始化的时候不需要修改?

    其实不难发现,初始化的合并是不会有重复贡献的情况的,即是每一次合并的区间是不会重叠的

  • 相关阅读:
    有关于阶乘的相关理解
    【JMeter】定时器分类以及场景介绍
    分布式ID生成服务的技术原理和项目实战
    [源码解析] TensorFlow 分布式之 ParameterServerStrategy V2
    Vue2前端权限控制实战
    1776_树莓派简介视频学习小结
    【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析
    [附源码]JAVA毕业设计基于MVC框架的在线书店设计(系统+LW)
    车联网解决方案-最新全套文件
    为什么要打日志?怎么打日志?打什么日志?
  • 原文地址:https://www.cnblogs.com/jeefy/p/17763318.html
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号