ST表学习笔记
目录
- 前言
- ST表简介
- ST表维护区间最大最小值
- 例题
1.前言
应老师要求,来写一篇关于ST表的学习笔记
2.ST表简介
ST 表是用于解决 可重复贡献问题 的数据结构。
—— oi-wiki
如何理解 可重复贡献?
举个例子:
给定 \(n\), \(m\),表示 \(n\) 个数,\(m\) 个询问,每次询问求 \([l,r]\) 内的最大值
我们发现,在 \(m\) 很小的时候,普通算法还能满足我们的要求。但当 \(m\) 取到 1e6 的时候,普通算法已经不能满足我们的需求了。
这时我们引入 ST表,它可以在 \(\mathcal{O}(n\) \(\text{log}n)\) 的时间预处理,然后在 \(\mathcal{O}(1)\) 的时间处理询问。
由于经过了预处理,所以当然可以重复贡献答案。
3. ST表维护区间最大最小值
想要预处理区间最大最小值,很自然地想到可以记录一个数组 \(f[i][j]\) 记录 \([i,j]\) 的最大/小值
很容易就可以想到转移方程:
但 \(\mathcal{O}(n^2)\) 的预处理时间显然不够优秀,需要想别的办法。
也是很容易就发现,\(max(a,b,c)=max(a,max(b,c))\)
所以我们可以用2个小区间推出一个大区间的最大值。
我们还可以运用倍增思想,令 \(f[i][j]\) 为从 \(a_i\) 开始,连续 \(2^j\) 个连续的数的最大值
所以也很容易得到转移方程:
处理询问的话,记询问区间长度为 len,我们从左端点向右找一段长为 \(2^{\log(len)}\) 的区间,右端点向左也找一段长为\(2^{\log(len)}\) 的区间,显然这两段区间已经覆盖了整个区间,取最大值即可。
我们可以提前预处理出每个 \(\log(len)\) 的值。
总时间复杂度: \(\mathcal{O}(n\) \(\log n +m)\)
4.例题
P3865 【模板】ST表
就是上面讲的
#include<bits/stdc++.h>
using namespace std;
int f[1000001][32],a,x,LC,n,k,p,len,l,r,ans2[1000001];
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;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
f[i][0]=a;
}
LC=(int)(log(n)/log(2));
for(int j=1;j<=LC;j++)
for (int i=1;i<=n-(1<<j)+1;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
int l,r;
for(int i=1;i<=k;i++)
{
l=read();
r=read();
p=(int)(log(r-l+1)/log(2));
printf("%d\n",max(f[l][p],f[r-(1<<p)+1][p]));
}
return 0;
}
P2880 [USACO07JAN] Balanced Lineup G
ST表预处理,每次询问求出最大最小值,相减即可
#include<bits/stdc++.h>
using namespace std;
int f[180001][32],f1[180001][32],a,x,LC,n,k,p,len,l,r;
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;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
f[i][0]=a;
f1[i][0]=a;
}
LC=(int)(log(n)/log(2));
for(int j=1;j<=LC;j++)
for (int i=1;i<=n-(1<<j)+1;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
for(int j=1;j<=LC;j++)
for (int i=1;i<=n-(1<<j)+1;i++)
f1[i][j]=min(f1[i][j-1],f1[i+(1<<(j-1))][j-1]);
int l,r;
for(int i=1;i<=k;i++)
{
l=read();
r=read();
p=(int)(log(r-l+1)/log(2));
printf("%d\n",max(f[l][p],f[r-(1<<p)+1][p])-min(f1[l][p],f1[r-(1<<p)+1][p]));
}
return 0;
}
P7809 [JRKSJ R2] 01 序列
设 \(s0_i\) 为 \(\sum_{j=1}^i a[j]=0\) , \(s1_i\) 为 \(\sum_{j=1}^i a[j]=1\)
对于第一问,答案就是 \(\max_{i=l}^r s0_i-s0_{l-1}+s1_r-s1_i\)
由于 \(s1_r-s0_{l-1}\) 值固定,所以就只用求 \(\max_{i=l}^r s0_i-s1_i\)
这东西可以用ST表维护
对于第二问,只有 \(1\) 和 \(2\) 两种答案,要有 \(2\) 则区间中必须出现连续的 \(0-1\),于是我们维护 \(0-1\) 出现次数的前缀和,如果左端点前缀和等于右端点前缀和,则答案为 \(1\),否则为 \(2\)。
#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
using namespace std;
namespace IO{//by cyffff
int len=0;
char ibuf[(1<<20)+1],*iS,*iT,out[(1<<26)+1];
#define gh() (iS==iT?iT=(iS=ibuf)+fread(ibuf,1,(1<<20)+1,stdin),(iS==iT?EOF:*iS++):*iS++)
#define reg register
inline int read(){
reg char ch=gh();
reg int x=0;
reg char t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=gh();
while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=gh();
return t?-x:x;
}
inline void putc(char ch){
out[len++]=ch;
}
template<class T>
inline void write(T x){
if(x<0)putc('-'),x=-x;
if(x>9)write(x/10);
out[len++]=x%10+48;
}
inline void flush(){
fwrite(out,1,len,stdout);
len=0;
}
}
using IO::read;
using IO::write;
using IO::flush;
using IO::putc;
const int N=5e6+7;
int a[N],f[N][32],s0[N],s1[N],lg[N],n,m,dot[N];
int cz(int l,int r)
{
int k=lg[r-l+1];
return max(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
n=read();
m=read();
// cin>>n>>m;
for(int i=1;i<=n;i++)
{
a[i]=read();
s1[i]=s1[i-1]+a[i];
s0[i]=s0[i-1]+(!a[i]);
if(a[i]==1&&!a[i-1]) dot[i]=dot[i-1]+1;
else dot[i]=dot[i-1];
}
lg[0]=-1;
for(int i=1;i<=n;i++)
{
lg[i]=lg[i>>1]+1;
f[i][0]=s0[i]-s1[i-1];
}
for(int j=1;j<=21;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]);
/*/
for(int i=1;i<=n;i++)
{
// a[i]=read();
cin>>a[i];
s1[i]=s1[i-1]+a[i];
if(!a[i]) s0[i]=s0[i-1]+1;
if(a[i]&&!a[i-1]) dot[i]=dot[i-1]+1;
else dot[i]=dot[i-1];
}
for(int i=1;i<=n;i++)
f[i][0]=s0[i]-s1[i-1];
lg[1]=0;
lg[2]=1;
for(int i=3;i<=n;i++)
lg[i]=(lg[i>>1]+1);
for(int i=1;i<=n;i++)
f[i][0]=s0[i]-s1[i-1];
for(int j=1;j<=21;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]);*/
while(m--)
{
int op,l,r;
// cin>>op>>l>>r;
op=read();
l=read();
r=read();
// cout<<op;
int ans=0;
if(op==1)
{
ans+=s1[r]-s0[l-1]+cz(l,r);
// cout<<ans<<endl;
write(ans);
putc('\n');
}
else
{
if(dot[l]==dot[r]) write(1);
else write(2);
putc('\n');
}
}
flush();
return 0;
}