ST学习小结
算法介绍
RMQ问题
给定数组A[0, N-1],找出给定的两个索引i和j间的最小值的位置。
ST(Sqarse Table)算法
是利用倍增的思想来解决RMQ问题的算法之一,适用于查询次数很大。
可用于得到区间的最小值和最大值。
用二维数组M[i][j]表示从下标i开始,长度为
2
j
2^{j}
2j的子数组的最小值的索引。
算法复杂度: 初始化
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) 和查询
O
(
n
)
O(n)
O(n)
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define T int T; scanf("%d", &T); while(T--)
typedef long long ll;
const int N=1e5+10;
const int M=50;
int n, a[N], b[N], stmin[N][M], stmax[N][M];
void init(){
b[0]=-1;//b[length]=j; length=2^j;
for(int i=1; i<=n; i++){
b[i] = ((i)&(i-1)==0) ? b[i-1]+1 : b[i-1];
stmax[i][0]=a[i];
stmin[i][0]=a[i];
}
for(int j=1; j<=b[n]; j++){
for(int i=1; i+(1<<j)-1<=n; i++){
stmax[i][j]=max(stmax[i][j-1], stmax[i+(1<<(j-1))][j-1]);
stmin[i][j]=min(stmin[i][j-1], stmin[i+(1<<(j-1))][j-1]);
}
}
}
int rmq_max(int l, int r){
int k=b[r-l+1];
return max(stmax[l][k], stmax[r-(1<<k)+1][k]);
}
int rmq_min(int l, int r){
int k=b[r-l+1];
return min(stmin[l][k], stmin[r-(1<<k)+1][k]);
}
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++){
scanf("%d", &a[i]);
}
init();
int q, a, b;
scanf("%d", &q);
while(q--){
scanf("%d%d", &a, &b);
printf("%d %d\n", rmq_max(a, b), rmq_min(a, b));
}
return 0;
}
练习(补题)
来一道题目练一练(其实是补题啦)
题目
这道题除了ST算法,还有双指针。
当某个区间的最大值与最小值之差已经大于k,那么固定i,j以后的区间都满足条件。然后i的位置再增加。此时为什么j不从1开始。假设j因为i位置的改变而从1开始,此时i和j的区间范围内,已经遍历过一遍。所以j没有必要从1开始。(这就是双指针)。如果一直没有满足差值大于k,则j一直增加j。
#include <bits/stdc++.h>
using namespace std;
#define T int T; scanf("%d", &T); while(T--)
typedef long long ll;
const int N=1e5+10;
const int M=25;
int n, m, a, b[N], st1[N][M], st2[N][M];
void init(){
b[0]=-1;
for(int i=1; i<=n; i++){
b[i]=(((i)&(i-1))==0) ? b[i-1]+1 : b[i-1];
}
for(int j=1; j<=b[n]; j++){
for(int i=1; i<=n-(1<<j)+1; i++){
st1[i][j]=min(st1[i][j-1], st1[i+(1<<(j-1))][j-1]);
st2[i][j]=max(st2[i][j-1], st2[i+(1<<(j-1))][j-1]);
}
}
}
bool diff(int l, int r, ll k){
int index=b[r-l+1];
int x=max(st2[l][index], st2[r-(1<<index)+1][index]);
int y=min(st1[l][index], st1[r-(1<<index)+1][index]);
if(x-y>k)
return true;
else
return false;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++){
scanf("%d", &a);
st1[i][0]=a;
st2[i][0]=a;
}
init();
ll k, ans;
while(m--){
ans=0;
scanf("%lld", &k);
for(int i=1, j=1; i<=n; i++){
while(!diff(i, j, k) && j<=n && i<=j){
j++;
}
if(j<=n)
ans+=n-j+1;
}
printf("%lld\n", ans);
}
return 0;
}
拓展
倍增思想还可用于解决LCA问题
当然,还是树链剖分更好
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
const int LOGMAXN=50;
int N, father[MAXN], P[MAXN][LOGMAXN], L[MAXN];
void preProcess(int N){//预处理
for(int i=0; i<N; i++){//对二维数组P进行初始化
for(int j=0; (1<<j)<N; j++){
P[i][j]=-1;
}
}
for(int i=0; i<N; i++)//路径为0时, P[i][0]为每一个叶结点
P[i][0]=father[i];
for(int j=1; (1<<j)<N; j++){//每个子结点分别想下跳出2^j的距离
for(int i=0; i<N; i++){
if(P[i][j-1]!=-1)
P[i][j]=P[P[i][j-1]][j-1];
}
}
}
int LCA(int u, int v){
int tmp, log, i;
if(L[u]<L[v])//使结点u为深度最深的结点
swap(u, v);
for(log=1; (1<<log)<=L[u]; log++);//得到从u到子结点的最大2^(j+1)
log--;
for(i=log; i>=0; i--){//使得u和v处于同一层
if(L[u]-(1<<i)>=L[v])
u=P[u][i];
}
if(u==v)//如果u恰好为v的father
return u;
for(i=log; i>=0; i--){//每次挑选能走的最大2^i
if(P[u][i]!=-1 && P[u][i]!=P[v][i]){//防止跳出范围
u=P[u][i];
v=P[v][i];
}
}
//最终的结果为距离lca(u, v)一步之遥,所以返回father[u]
return father[u];
}