【寻迹#6】ST表
ST表
一、简介
ST 表基于 倍增思想,可以做到 \(O(n\log n)\) 预处理, \(O(1)\) 回答每个询问。但是不支持修改操作。
基于倍增思想,我们考虑如何求出区间最大值。可以发现,如果按照一般的倍增流程,每次跳 \(2^i\) 步的话,询问时的复杂度仍旧是 \(O(\log n)\) ,并没有比线段树更优,反而预处理一步还比线段树慢。
我们发现 \(\max(x,x)=x\) ,也就是说,区间最大值是一个具有「可重复贡献」性质的问题。即使用来求解的预处理区间有重叠部分,只要这些区间的并是所求的区间,最终计算出的答案就是正确的。
具体的实现就是令 \(f_{i,j}\) 表示区间 \([i,i+2^j-1]\) 的最大值,
显然 \(f_{i,0}=a_i\) ,
然后根据倍增的思路,写出状态转移方程 \(f_{i,j}=\max(f_{i,j-1},f_{i+2^{j-1},j-1})\) 。
以上就是预处理部分,然后对于查询,也可以很简单实现,
对于每一个区间 \([l,r]\) ,我们把它分成两部分: \([l,l+2^k-1]\) 以及 \([r-2^k+1,r]\) ,其中 \(k=\left\lfloor\log_2{r-l+1}\right\rfloor\) ,两部分的结果的最大值就是回答。
根据上面对于「可重复贡献问题」的论证,由于最大值是「可重复贡献问题」,重叠并不会对区间最大值产生影响。又因为这两个区间完全覆盖了\([l,r]\) ,可以保证答案的正确性。
二、题单
1.【模板】ST 表 && RMQ 问题
思路:模板题直接按照上面所说实现即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 100050
int n,m,a[N];
int k,l,r;
int f[N][20];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') f=-1;ch=getchar(); }
while(ch>='0'&&ch<='9') { x=x*10+ch-48;ch=getchar(); }
return x*f;
}
inline void ST()
{
k=log2(n);
for(int j=1;j<=k;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
inline int RMQ(int l,int r)
{
k=log2(r-l+1);
return max(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
n=read();
m=read();
for(int i=1;i<=n;i++)
{
cin>>a[i];
f[i][0]=a[i];
}
ST();
while(m--)
{
l=read();r=read();
printf("%d\n",RMQ(l,r));
}
return 0;
}
2.质量检测
思路:没区别,预处理还是ST表。查询的时候就是区间长度一直,开个for循环平推过去即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 1000050
int n,m,a[N];
int k;
int f[N][35];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') f=-1;ch=getchar(); }
while(ch>='0'&&ch<='9') { x=x*10+ch-48;ch=getchar(); }
return x*f;
}
inline void ST()
{
k=log2(n);
for(int j=1;j<=k;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
inline int RMQ(int l,int r)
{
k=log2(r-l+1);
return min(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
f[i][0]=a[i];
}
ST();
for(int i=1;i<=n-m+1;i++) printf("%d\n",RMQ(i,i+m-1));
return 0;
}
3.忠诚
思路:还是一道板子题,ST表维护最小值即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 100050
int n,m,a[N];
int k,l,r,cnt;
int f[N][35],ans[N];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') f=-1;ch=getchar(); }
while(ch>='0'&&ch<='9') { x=x*10+ch-48;ch=getchar(); }
return x*f;
}
inline void ST()
{
k=log2(n);
for(int j=1;j<=k;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
inline int RMQ(int l,int r)
{
k=log2(r-l+1);
return min(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
f[i][0]=a[i];
}
ST();
while(m--)
{
l=read();r=read();
ans[++cnt]=RMQ(l,r);
}
for(int i=1;i<=cnt;i++) cout<<ans[i]<<" ";
return 0;
}
4.[USACO07JAN] Balanced Lineup G
思路:维护两个ST表即可,一个最大值一个最小值。然后减掉即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 50050
int n,q,h[N];
int k,l,r;
int f1[N][20],f2[N][20];
inline void ST()
{
k=log2(n);
for(int j=1;j<=k;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f1[i][j]=max(f1[i][j-1],f1[i+(1<<(j-1))][j-1]);
for(int j=1;j<=k;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f2[i][j]=min(f2[i][j-1],f2[i+(1<<(j-1))][j-1]);
}
inline int RMQ_max(int l,int r) { return max(f1[l][k],f1[r-(1<<k)+1][k]); }
inline int RMQ_min(int l,int r) { return min(f2[l][k],f2[r-(1<<k)+1][k]); }
int main()
{
cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>h[i];
f1[i][0]=h[i];
f2[i][0]=h[i];
}
ST();
while(q--)
{
cin>>l>>r;
k=log2(r-l+1);
cout<<RMQ_max(l,r)-RMQ_min(l,r)<<endl;
}
return 0;
}
5.最大数
思路:
一道还算是有意思的题目,按理来说ST表是不支持修改操作的,但是你发现这道题并不是对已有的ST表进行修改,而是不断地在末尾插入一个新的数字,所以还是可以使用ST表来做,只不过我们令 \(f_{i,j}\) 表示第 \(i\) 个位置往前跳 \(2^j-1\) 步的ST表,相当于把原先的ST表倒过来了,然后就做完了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 200050
ll m,d,n;
ll t,cnt,a[N],l,r,k;
ll f[N][20];
inline void modify(ll x)
{
x=(x+t%d)%d;
a[++cnt]=x;
f[cnt][0]=x;
for(ll i=1;cnt-(1<<i)+1>=0;i++)
f[cnt][i]=max(f[cnt][i-1],f[cnt-(1<<(i-1))][i-1]);
}
inline ll RMQ(ll l,ll r)
{
k=log2(r-l+1);
return max(f[r][k],f[l+(1<<k)-1][k]);
}
int main()
{
cin>>m>>d;
for(ll i=1;i<=m;i++)
{
char opt;
cin>>opt>>n;
while(n<0) n+=d;
n%=d;
if(opt=='A') modify(n);
else
{
r=cnt;l=cnt-n+1;
t=RMQ(l,r)%d;
cout<<t<<endl;
}
}
return 0;
}