ST表(保姆级,简单易懂)

文章介绍了ST表,一种利用动态规划解决区间查询问题的数据结构,能在O(1)时间复杂度内找到区间内的最值。通过递推和区间划分,文章详细展示了如何构建ST表并进行区间最值查询。

介绍

ST表(Sparse Table)是一种用于高效处理区间查询的数据结构。它可以在O(1)的时间复杂度内回答某一区间的最值查询(最小值、最大值等)。ST表使用动态规划的思想,通过预处理的方式来快速计算出各个区间的最值。

引入

假设有8个数据,如何找出极差?

例:3 5 2 5 7 8 1 9

通过部分区间的最值推导出整个区间的最值,同样我们可以求出最小值1,极差就是8。

这个操作我们可以逆向考虑,将区间一分为二,要找出3 5 2 5和7 8 1 9的最值就需要找出 3 5,2 5,7 8 ,1 9的最值。

那么我们可以对一个包含2^j的数字的区间[i,i+2^j-1](注意要减1,可以举例子理解进行维护。

将区间一分为二,因为数据是离散的,所以端点我们要考虑一下。可以得出,第一个区间以i为起点,包含2^(j-1)个数,第二个区间以i+2^(j-1)为起点,包含2^(j-1)个数总包含2^j个数(不明白可以举例,如1 2 3 4

建表

定义st[i][j]为以i为起点,包含2^j个数的区间。由引入部分的图解,我们可以写出递推式

st[i][j] = max (st[i][j - 1] , st[i + (1 << j - 1)][j - 1]);
//1 << j - 1 就是 2 ^ (j - 1),代码一定要这么写。 

因为递推需要知道子区间的最值,最小子区间就是单个数字,也就是st[i][0],所以需要先对st[i][0]赋初始值。然后从j=1开始,枚举所有包含2^j个数字的区间。代码如下

for (int i = 1; i <= n; i++)  cin >> stmax[i][0];
for (int j = 1; (1 << j) <= n; j++)//总包含数字不超过n
	{
		for (int i = 1; i + (1 << j) - 1 <= n; i++)//确保是子区间,不越界
		{
			stmin[i][j] = min(stmin[i][j - 1], stmin[i + (1 << j - 1)][j - 1]);
			stmax[i][j] = max(stmax[i][j - 1], stmax[i + (1 << j - 1)][j - 1]);
		}
	}

查表 

定义两个变量l,r为查找区间的左右端点。要查区间的最值,只需分别查如图两段包含2^x个数的区间的最值,再进行比较,就得到所查区间的最值。为此,我们必须保证,两端区间必须包含了所有所查区间的数。因此我们需要得出最大的x。2^x<2^j2^x<r-l+1,得出x<log2(r-l+1),x最大就向下取整。两端区间的端点分别是lr-(1<<x)+1

代码如下

int l, r;
cin >> l >> r;
int x = log_2[r - l + 1];
cout << max(st[l][x], st[r - (1 << x) + 1][x] << endl;

log2向下取整函数自己写即可,在下面题目的代码中,自行消化。

水题一道熟练一下

代码如下:

#include<iostream>
using namespace std;
int n, q;
const int MAXN = 5e4+5,MAX_L = 20;
int log_2[MAXN];
int stmin[MAXN][MAX_L];
int stmax[MAXN][MAX_L];

int main()
{
	cin >> n >> q;
	log_2[0] = -1;
	for (int i = 1; i <= n; i++)
	{
		cin >> stmax[i][0];
		stmin[i][0] = stmax[i][0];
		log_2[i] = log_2[i >> 1] + 1;
	}
	for (int j = 1; (1 << j) <= n; j++)
	{
		for (int i = 1; i + (1 << j) - 1 <= n; i++)
		{
			stmin[i][j] = min(stmin[i][j - 1], stmin[i + (1 << j - 1)][j - 1]);
			stmax[i][j] = max(stmax[i][j - 1], stmax[i + (1 << j - 1)][j - 1]);
		}
	}
	while(q--)
	{
		int l, r;
		cin >> l >> r;
		int x = log_2[r - l + 1];
		cout << max(stmax[l][x], stmax[r - (1 << x) + 1][x]) - min(stmin[l][x], stmin[r - (1 << x) + 1][x]) << endl;
	}
}

感谢阅读~ 

<think>嗯,用户想找关于C++中树状数组的详细且易懂的教程。根据之前的引用内容,我需要综合引用1、2、3的信息来构建回答。首先,用户可能对树状数组的基本概念不太清楚,所以得先解释清楚它的定义和用途。然后需要介绍它的核心操作,比如查询和更新,特别是lowbit函数的作用。接下来,代码示例是必不可少的,用户可能想看到具体的C++实现,包括初始化和操作步骤。最后,应用场景和优缺点也需要涵盖,这样用户能全面了解何时使用树状数组。 用户提到的引用1和2都强调了树状数组的查询和修改复杂度为O(log n),主要用于单点修改和区间求和,这点很重要。引用3提供了具体的代码示例,尤其是update函数的实现,这部分应该详细说明。同时,用户可能对树状数组与线段树的区别感兴趣,所以需要对比两者,指出树状数组的优缺点。 另外,用户希望教程详细且易懂,所以需要分步骤解释,可能还需要图示辅助理解树状数组的结构,但文本中可能无法插入图片,所以需要用文字描述结构。例如,树状数组如何将数组元素存储在树状结构中,每个节点管理特定区间的和。 还要注意回答的结构是否清晰,是否符合用户的要求。用户可能对实现细节和原理有疑问,比如lowbit函数的计算、更新和查询的过程如何工作。需要确保每个部分都解释清楚,避免使用过于专业的术语,保持语言通俗易懂。 最后,生成的相关问题要覆盖树状数组的各个方面,比如应用场景、与线段树的对比、具体实现中的函数作用等,这样用户如果有进一步的问题,可以自行探索。</think>### C++树状数组详细教程 ### 一、树状数组基础概念 树状数组(Binary Indexed Tree,BIT)是一种支持**单点修改**和**区间查询**的高效数据结构,时间复杂度均为$O(\log n)$[^2]。其核心思想是将数组转化为树状结构,每个节点存储特定区间的和。 ### 二、核心操作实现 #### 1. lowbit函数 定义:计算最低有效位的值 ```cpp int lowbit(int x) { return x & (-x); // 等价于x & (~x + 1) } ``` 例如: $lowbit(6)=2$,因为$6=110_2$,最低有效位是$10_2=2$[^3] #### 2. 更新操作 单点增加数值: ```cpp void update(int pos, int val) { while(pos <= n) { tree[pos] += val; pos += lowbit(pos); } } ``` #### 3. 查询操作 前缀和查询: ```cpp int query(int pos) { int res = 0; while(pos > 0) { res += tree[pos]; pos -= lowbit(pos); } return res; } ``` ### 三、完整代码示例 ```cpp #include <iostream> using namespace std; const int N = 1e5+5; int tree[N], n; int lowbit(int x) { return x & (-x); } void update(int pos, int val) { while(pos <= n) { tree[pos] += val; pos += lowbit(pos); } } int query(int pos) { int res = 0; while(pos > 0) { res += tree[pos]; pos -= lowbit(pos); } return res; } int main() { cin >> n; int a; for(int i=1; i<=n; i++) { cin >> a; update(i, a); } // 查询区间[2,5]的和 cout << query(5) - query(1) << endl; return 0; } ``` ### 四、应用场景与对比 | 场景 | 树状数组 | 线段树 | |--------------------|----------|--------| | 单点修改+区间求和 | ✅ | ✅ | | 区间修改+单点查询 | ✅ | ✅ | | 区间修改+区间求和 | ❌ | ✅ | | 二维操作 | ✅ | ✅ | | 代码复杂度 | 简单 | 复杂 | ### 五、进阶技巧 1. **逆序对统计**:通过离散化+树状数组 2. **区间最值**:需要修改更新逻辑 3. **差分数组**:实现区间修改 4. **多维扩展**:二维树状数组
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值