算法笔记——ST表
ST表
ST表是一种简单的数据结构,主要用于解决RMQ问题(区间最大/最小值问题)主要应用倍增的思想,可以实现O(nlogn)预处理,O(1)查询
1.预处理ST表
倍增法递推:用两个等长的小区间拼凑一个大区间
f[i][j]表示以第i个数为起点,长度为2^j的区间里的最大值/最小值
f[i][j]=max(f[i][j-1],f[i+2^j-1][j-1])
区间终点:i+2^j-1 <= n
代码实现
for(int i=1;i<=n;i++)
cin>>f[i][0];
for(int j=1;j<=20;j++)//先枚举区间长度的指数
{
for(int i=1;i+(1<<j)-1<=n;i++)//枚举起点 i+(1<<j)-1是区间的终点要受到上界n的限制
{
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);//在这段代码中所举的例子是最大值,改成min(,)就是最小值,改成gcd()就是最大公约数。。类似
}
}
对于此算法外层时间复杂度是O(logn),内层是O(n)的,总的时间复杂度就是O(nlogn)的
比如 n=6;
f[i][0]: f[1][1] f[2][2] f[3][3] f[4][4] f[5][5] f[6][6]
f[i][1]: f[1][2] f[2][3] f[3][4] f[4][5] f[5][6]
f[i][2]: f[1][4] f[2][5] f[3][6]
if j==3:i+2^j-1=1+8-1= 8 > 6
2.处理询问
对查询区间 [l,r]做分割,拼凑,
区间长度的指数 k=log2(r-l+1) (注意:这一运算是下取整的)
我们列出所有的情况:
//区间长度可能的情况
k = 0: {1}
k = 1: {2,3}
k = 2: {4,5,6,7}
k = 3: {8,9,10,...,15}
由此:2^k <= r-l+1 < 2*2^k,即 区间 [l,r]必可以用两个长度为2^k的区间来 重叠拼凑 出来!
代码实现
while(T--)
{
int l,r;
cin>>l>>r;
int k=log2(r-l+1);
cout<<max(f[l][k],f[r - (1<<k) + 1][k])<<endl;
}
3.拓展
ST表不仅能够解决 RMQ问题,更可以解决所有符合结合律且可重复贡献的信息查询都可以使用ST表高效进行。
可重复贡献:设有一个二元运算f(x,y) 满足 f(a,a) = a,则运算f是可重复贡献的,显然最大值,最小值,最大公约数,按位或,按位与都符合这个条件。
可重复贡献的意义在于,可以对两个交集不为空的区间进行信息合并。
例题:
洛谷:P2880 [USACO07JAN] Balanced Lineup G
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
int maxx[N][21];
int minn[N][21];//注意第二位代表的数字是j是区间长度的log因此不需要开太大
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>maxx[i][0];
minn[i][0]=maxx[i][0];
}
for(int j=1;j<=20;j++)
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
maxx[i][j]=max(maxx[i][j-1],maxx[i+(1<<(j-1))][j-1]);
minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
}
}
int l,r;
while(q--)
{
cin>>l>>r;
int k=log2(r-l+1);
cout<<max(maxx[l][k],maxx[r - (1<<k) + 1][k])-min(minn[l][k],minn[r - (1<<k) + 1][k])<<endl;
}
return 0;
}