ST 表学习笔记

引入

有时候我们需要静态查询一个序列上的区间最大 / 最小值问题,而它显然可以用线段树维护。
但是线段树太麻烦了,常数还大,更何况没有修改时用在这上面简直是杀鸡用牛刀……
所以有一种常数小得多的算法叫做 S T ST ST

思路

我们用 s t j , i st_{j,i} stj,i 表示从序列中第 j j j 个数到第 j + 2 i − 1 j + 2^i - 1 j+2i1 个数中的最大值(区间长度为 2 i 2^i 2i ,初始化 s t j , 0 = a j st_{j,0} = a_j stj,0=aj a a a 为原序列),则运用倍增的思想将区间 ( j , j + 2 i − 1 ) (j,j + 2^i - 1) (j,j+2i1) 由它的两个长度为 2 i − 1 2^{i-1} 2i1 的不交的子区间合并而来,即 s t j , i = max ⁡ ( s t j , i − 1 , s t j + 2 i − 1 , i − 1 ) st_{j,i} = \max(st_{j,i - 1},st_{j+2^{i-1},i - 1}) stj,i=max(stj,i1,stj+2i1,i1)

那查询又该怎样处理呢?
假设要查询的区间为 ( x , y ) (x,y) (x,y) ,长度为 l e n len len ,记 k = ⌊ ln ⁡ ( l e n ) ⌋ k = \lfloor\ln(len)\rfloor k=ln(len)⌋ ,则可以将区间视为一个从 x x x x + 2 k x + 2^k x+2k 的区间与一个从 y − 2 k + 1 y - 2^k + 1 y2k+1 y y y 的区间的并。这两个区间肯定会有重叠,但是这不重要。首先,这两个字区间覆盖了整个原区间,同时也不会超出原区间。其次,区间 m a x max max 被多次统计得到的答案也不会有任何变化

实现

一个 模板 代码

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
#define rpt(i,a,n) for(int i = a;i <= n;++i)
const int N = 2e5 + 5,M = log2(N) + 5;
int st[N][M],lg[N],n,m,x,y;
inline int query(int x,int y){
	int k = lg[y-x+1];//两个子区间的长度
	int m = max(st[x][k],st[y-(1<<k)+1][k]);//合并两个子区间的信息
	return m;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	rpt(i,2,n) lg[i] = lg[i>>1] + 1;//求 ln(i)
	rep(i,n) cin>>st[i][0];//相当于给 st 表赋初值
	rep(i,lg[n]) rep(j,n + 1 - (1 << i))
		st[j][i] = max(st[j][i-1],st[j+(1<<(i-1))][i-1]);//初始化
	while(m--){
		cin>>x>>y;
		cout<<query(x,y)<<'\n';
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值