算法学习笔记(3.1): ST算法
ST表
在RMQ(区间最值)问题中,著名的ST算法就是倍增的产物。ST算法可以在 \(O(n \log n)\) 的时间复杂度能预处理后,以 \(O(1)\) 的复杂度在线回答区间 [l, r] 内的最值。
当然,ST表不支持动态修改,如果需要动态修改,线段树是一种良好的解决方案,是 \(O(n)\) 的预处理时间复杂度,但是查询需要 \(O(\log n)\) 的时间复杂度
那么ST表中倍增的思想是如何体现的呢?
一个序列的子区间明显有 \(n^2\) 个,根据倍增的思想,我们在这么多个子区间中选择一些长度为 \(2\) 的整数次幂的区间作为代表值。
设 \(st[i][j]\) 表示子区间 \([i, i+2^j)\) 里最大的数
也可以表示为 \([i, i + 2^j -1 ]\),无论如何,其中有 \(2^j\) 个元素
下文中的 \(a\) 表示原序列
递推边界明显是 \(st[i][0] = a[i]\)。
于是,根据成倍增长的长度,有了递推公式
\]
当询问任意区间 \([l, r]\) 的最值时,我们先计算出一个最大的 \(k\) 满足:\(2^k \le r - l + 1\),即需要不大于区间长度。那么,由于二进制划分我们可以知道,这个最大的 k 一定满足 \(2^{k+1}\ge r-l+1\),即我们只需要将两个长度为 \(2^k\) 的区间合并即可。
又根据 max(a, a) = a 可以知道,重复计算区间是没有任何问题的。
所以,在寻找最值的时候就有了以下公式:
\]
那么这里给出一种参考代码
// 啊,写这种预处理以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 的情况了。
其实这是借鉴了一点线段树的思路
还不如直接用线段树……
那么可以写出以下代码
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))
}
这样就满足了区间不重叠
或许会有一个问题,为什么初始化的时候不需要修改?
其实不难发现,初始化的合并是不会有重复贡献的情况的,即是每一次合并的区间是不会重叠的
算法学习笔记(3.1): ST算法的更多相关文章
- 算法学习笔记(3): 倍增与ST算法
倍增 目录 倍增 查找 洛谷P2249 重点 变式练习 快速幂 ST表 扩展 - 运算 扩展 - 区间 变式答案 倍增,字面意思即"成倍增长" 他与二分十分类似,都是基于" ...
- 算法学习笔记(5): 最近公共祖先(LCA)
最近公共祖先(LCA) 目录 最近公共祖先(LCA) 定义 求法 方法一:树上倍增 朴素算法 复杂度分析 方法二:dfs序与ST表 初始化与查询 复杂度分析 方法三:树链剖分 DFS序 性质 重链 重 ...
- C / C++算法学习笔记(8)-SHELL排序
原始地址:C / C++算法学习笔记(8)-SHELL排序 基本思想 先取一个小于n的整数d1作为第一个增量(gap),把文件的全部记录分成d1个组.所有距离为dl的倍数的记录放在同一个组中.先在各组 ...
- Manacher算法学习笔记 | LeetCode#5
Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...
- 机器学习实战(Machine Learning in Action)学习笔记————08.使用FPgrowth算法来高效发现频繁项集
机器学习实战(Machine Learning in Action)学习笔记————08.使用FPgrowth算法来高效发现频繁项集 关键字:FPgrowth.频繁项集.条件FP树.非监督学习作者:米 ...
- 机器学习实战(Machine Learning in Action)学习笔记————07.使用Apriori算法进行关联分析
机器学习实战(Machine Learning in Action)学习笔记————07.使用Apriori算法进行关联分析 关键字:Apriori.关联规则挖掘.频繁项集作者:米仓山下时间:2018 ...
- 机器学习实战(Machine Learning in Action)学习笔记————02.k-邻近算法(KNN)
机器学习实战(Machine Learning in Action)学习笔记————02.k-邻近算法(KNN) 关键字:邻近算法(kNN: k Nearest Neighbors).python.源 ...
- [ML学习笔记] 朴素贝叶斯算法(Naive Bayesian)
[ML学习笔记] 朴素贝叶斯算法(Naive Bayesian) 贝叶斯公式 \[P(A\mid B) = \frac{P(B\mid A)P(A)}{P(B)}\] 我们把P(A)称为"先 ...
- Effective STL 学习笔记 31:排序算法
Effective STL 学习笔记 31:排序算法 */--> div.org-src-container { font-size: 85%; font-family: monospace; ...
- Johnson算法学习笔记
\(Johnson\)算法学习笔记. 在最短路的学习中,我们曾学习了三种最短路的算法,\(Bellman-Ford\)算法及其队列优化\(SPFA\)算法,\(Dijkstra\)算法.这些算法可以快 ...
随机推荐
- Java 集合框架体系简介
为什么要使用集合 存储多个数据可以使用数组,但由于数组在内存中是连续存储的,所以会有一些限制.比如数组在创建时就要指定长度,即可以容纳的元素个数,且指定后无法更改:数组在创建时需要指定元素的类型,并且 ...
- Prometheus-3:一文详解promQL
读前提示: 本文字数较多且紧凑,最好预留15min一次性看完,好营养,易吸收. promQL详解 Prometheus提供了内置的数据查询语言PromQL(全称为Prometheus Query La ...
- vue3+ts使用v-for出现unknown问题
最近在写项目时遇到了一个问题,当我从父组件向子组件传数据并且需要将子组件对传入的数据进行v-for循环渲染时,在此出遇到了一个ts报错 报错为循环出的data类型为unknown 具体代码如下 : 子 ...
- 输入平方米的三种方式㎡ m2 m²
如何在Word中输入平方米字符? 第1种方法 Win10自带输入法,输入"平方米",默认第5个就出来了㎡,也可以直接复制使用. 这种方式最直接,字母m和右上角的2是1个字符,所以不 ...
- 关于quartus II的导入以前的工程,QSF文件出现的错误的解决方案。
在有时候打开以前的工程,或者别人做好的例程会遇到一些报错信息.具体报错信息如下: 报错信息语句行: 在文件QSF文件中有几行出错,显示错误读取,即不能打开工程.打开文件发现该几行的PIN 使能信号处于 ...
- 达梦数据库: SQL查询报错《不是 GROUP BY 表达式解决方法》
报错信息: ****: 第4 行附近出现错误: 不是 GROUP BY 表达式 修改办法: 达梦可以配置兼容参数,COMPATIBLE_MODE=4,静态参数,需要重启数据库后生效! sp_set_p ...
- 你真的知道吗?catch、finally和return哪个先执行
我的一位朋友前阵子遇到一个问题,问题的核心就是try--catch--finally中catch和finally代码块到底哪个先执.这个问题看起来很简单,当然是"catch先执行.final ...
- [pytest]基础
简介 The pytest framework makes it easy to write small, readable tests, and can scale to support compl ...
- Jmeter+Ant+Jenkins接口自动化测试平台
一个完整的接口自动化测试平台需要支持接口的自动执行,自动生成测试报告,以及持续集成. Jmeter 支持接口的测试, Ant 支持自动构建,而 Jenkins 支持持续集成,所以三者组合在一起可以构成 ...
- C#程序变量统一管理例子 - 开源研究系列文章
今天讲讲关于C#应用程序中使用到的变量的统一管理的代码例子. 我们知道,在C#里使用变量,除了private私有变量外,程序中使用到的公共变量就需要进行统一的存放和管理.这里笔者使用到的公共变量管理库 ...