SKULL

一言(ヒトコト)

【寻迹#6】ST表

ST表

一、简介

ST 表基于 倍增思想,可以做到 \(O(n\log n)\) 预处理, \(O(1)\) 回答每个询问。但是不支持修改操作。

基于倍增思想,我们考虑如何求出区间最大值。可以发现,如果按照一般的倍增流程,每次跳 2^i\(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;
}
posted @ 2024-12-13 17:26  A&K.SKULL  阅读(14)  评论(0编辑  收藏  举报