思路
f f f 数组介绍
倍增思想的数组—— f f f。 f f f数组中, f i , j f_{i,j} fi,j表示以第 i i i号节点为开头,向后 2 j 2^j 2j个数中的最大值(不一定是最大值,题目问你什么求什么)。
初始化
以求区间最大值为例。
初始化 f f f数组为-1(若有负数,这位 − i n f -inf −inf)对于所有的 f i , 0 f_{i,0} fi,0,全部定义为 a i a_i ai本身。因为从第 i i i个元素到第 i i i个元素的最大值就是它本身。接着,先枚举 j j j,即2的指数,再枚举 i i i,即开头的元素,目的是可以保证枚举到的区间可以由已经枚举过的任意了两个区间或更多区间拼合而成。在更新 f f f值前,先要判断该区间的右端点是否越界(即超过元素个数的大小)。然后更新,更新时取大小为 2 j − 1 2^{j-1} 2j−1大小的正好能完全拼合成现在这个区间的两个区间。代码表述为:
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1])
查询
同样以查询区间 [ l , r ] [l,r] [l,r]最大值为例。
查询通常有三种方法,个人较为推荐两种:
1.完全覆盖无重叠;
2.有重叠覆盖。
若不是求区间次大值、区间次小值等不能重复的问题,一般选择第二种,毕竟第二种的复杂度
Θ
(
1
)
\Theta (1)
Θ(1)小于第一种的
Θ
(
l
o
g
l
e
n
)
\Theta (log_{len})
Θ(loglen),并且代码简洁易写,只需要用到一个换底公式即可。求出最大的并且
2
j
2^j
2j小于(等于)
l
e
n
len
len的
j
j
j值,答案就是
l
l
l向后
2
j
2^j
2j个元素的最大值与
r
r
r向前
2
j
2^j
2j个元素的最大值的较大值。代码表述为:
int len=r-l+1;//求区间长度
int k=log(len)/log(2);//换底公式
printf("%d\n",max(f[l][k],f[r-(1<<k)+1][k]));//求答案
代码
思路前面都已经讲过,所以这里直接上代码(求区间最大值),看不懂请重新看这里。
初始化
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++){
f[i][0]=read();
}
for(int j=1;j<=20;j++){
for(int i=1;i<=n;i++){
if(i+(1<<j)-1<=n){
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
查询
for(int i=1;i<=q;i++){
l=read();
r=read();
int len=r-l+1;
int k=log(len)/log(2);
printf("%d\n",max(f[l][k],f[r-(1<<k)+1][k]));
}
ST表例题
题目链接:【模板】ST表。
思路
这道题不就是把刚才讲的所有思路与代码拼凑起来,这里不再赘述。
需要注意的是:
- 这道题不开快读过不了。
- 这道题用
cout
过不了。 - 其他没了。
代码
声明:仅供参考。
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,q,f[100010][20],l,r;
int main(){
memset(f,-1,sizeof(f));
n=read();
q=read();
for(int i=1;i<=n;i++){
f[i][0]=read();
}
for(int j=1;j<=20;j++){
for(int i=1;i<=n;i++){
if(i+(1<<j)-1<=n){
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
for(int i=1;i<=q;i++){
l=read();
r=read();
int len=r-l+1;
int k=log(len)/log(2);
printf("%d\n",max(f[l][k],f[r-(1<<k)+1][k]));
}
return 0;
}