Loading

ST表学习笔记

目录

  1. 前言
  2. ST表简介
  3. ST表维护区间最大最小值
  4. 例题

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]\) 的最大/小值
很容易就可以想到转移方程:

\[f[i][j]=max(f[i][j-1],a[j]) \]

\(\mathcal{O}(n^2)\) 的预处理时间显然不够优秀,需要想别的办法。
也是很容易就发现,\(max(a,b,c)=max(a,max(b,c))\)
所以我们可以用2个小区间推出一个大区间的最大值。
我们还可以运用倍增思想,令 \(f[i][j]\) 为从 \(a_i\) 开始,连续 \(2^j\) 个连续的数的最大值
所以也很容易得到转移方程:

\[f[i][0]=a_i \]

\[f[i][j]=max(f[i][j-1],f[i+2^{j-1}][j-1]) \]

处理询问的话,记询问区间长度为 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;
}
posted @ 2022-05-28 15:50  sheeplittlecloud  阅读(105)  评论(0编辑  收藏  举报