题目链接 https://www.luogu.org/problem/P3865
原文 https://www.luogu.org/blog/loi-syc/p3865-mu-ban-st-biao
这个题是很裸的st表,让我们先讲讲st表是个啥东西:
st表(RMQ)--- O(nlogn+Q)算法:
原理:把给定区间分成长度是2的幂次的小区间。先预处理出它们中的最小值是多少,然后用一种类似二分的思想由小区间到大区间比较两个区间的最小值。
主要变量:
1.f[i][j] : 记录给定序列中区间[i,i+pow(2,j)-1]中的最大值。
起始条件:f[i][0]=a[i];
(根据上面的定义,f[i][0]代表[i,i]区间最小值(即a[i]:显然区间[i,i]里只有它一个数))。
2.bit[i] : bit[i]=pow(2,i)(记录2的i次方)。
起始条件:bit[0]=1,之后for循环生成即可。
3.a[i]:存给定序列。
4.LC:在生成st表时作为f[i][j]中j的循环上界。
在正式讲st表之前先看个模拟加深一下理解:
根据f[i][j]的定义,我们可以针对[1,5]模拟处理出所有长度为1,2,4的区间的最小值
长度为1的区间[1,1],[2,2],[3,3],[4,4],[5,5]
长度为2的区间[1,2],[2,3],[3,4],[4,5]
长度为4的区间[1,4],[2,5]
用f[i][j]表示:
E:长度为1的区间: f[1][0] <=>[1,1]
f[2][0] <=>[2,2]
E:长度为2的区间: f[1][1] <=>[1,2]
f[2][1] <=>[2,3]
f[1][2] <=>[1,4]
f[2][2] <=>[2,5]
总结一下:
1.在f[i][j]中,i为区间左端点,j决定了f[i][j]包括的比较过的的元素个数为2^j,区间右端点下标为i+pow(2,j)-1(注意这里有个-1);
2.由于我们循环生成st表时按区间长度由小到大,故先循环j(1~(int)log2(n))------ (此处的j是f[i][j]中的j)。
下面进入正题:
-
如何求出f数组
f[i][j]:从i出发算上i连续pow(2,j)个数的最大值
<=>从i出发连续pow(2,j-1) 与 从i+pow(2,j-1)出发连续pow(2,j-1)这两个最大值的最大值(分成两半)
方程:
f[i][j]=max(f[i][j-1],f[i+pow(2,j-1)][j-1]);
//1.生成st表(nlogn):
```cpp
LG=(int)(log(n)/log(2));//自动向下取整(显然向上取整搞st表时比较最大值时容易把给定序列外头的元素也包括在内,不过这对于最大值么什么大碍,如果是查找最小值。。。你就杯具了)
//log(n)/log(2)<=>log2(n)---换底公式
//换底公式:loga b=lg b/lg a
for (i=1; i<=LG; i++)
bit[i]=bit[i-1]*2;//初始化bit[i]
bit[0]=1;
for (i=1; i<=n; i++)
f[i][0]=a[i];//起始化
for (j=1; j<=LG; j++)//由于2^j为区间长度,故循环j其实就是把区间长度从1枚举到n(有可能<)
for (i=1; i<=n-bit[j]+1; i++)//对于每一个区间长度枚举所有可行的左端点
//i<=n-bit[j]+1------n-bit[j]+1+2^j-1=n
f[i][j]=max(f[i][j-1],f[i+bit[j-1]][j-1]);//方程
讲完了生成,再来个模拟大概理解一下st表的生成:
给定一个数列2 4 3 5 1:
此时LC=2,n=5;
f[1][0]=2 f[2][0]=4 f[3][0]=3 f[4][0]=5 f[5][0]=1
当j=1时, i = 1 to 4
f[1][1]=max(f[1][0],f[2][0])=4;[1,2]
f[2][1]=4
f[3][1]=5
f[4][1]=5
当j=2时, i = 1 to n-bit[j]+1=2
f[1][2]=max(f[1][1],f[3][1])=5;
f[2][2]=5
-
如何给定任意l,r,访问[l,r]中最小值?
由于我们不能保证r就=l+2^x-1(x任意),显然我们应该让以l为起点的st表数据覆盖[l,r]中数足够多,
规定在f[i][j]中,i=l,j=p,len=r-l+1(区间长度);
找最大的p应满足2^p <= len(以l为起点的st表数据覆盖[l,r]中数足够多)
则p=int(log(len)/log(2))(再次提醒:向上取整会越界);
example:对于区间[4,8],p=2,分到[4,4+2^2-1],即[4,7];
因为f[l]p中最大的p满足l+pow(2,p)-1<=r
故查询时分的查询区间[l,r]的右区间的左端点x=r+1-pow(2,p)
如何求得这个x:
对于f[x][r],应有:[x][x+pow(2,p)-1]<=>[x][r]
所以, x+pow(2,p)-1=r
移项,得:x=r+1-pow(2,p)
显然,x>=l (l+pow(2,p)-1<=r,x+pow(2,p)-1=r,故x>=l)
综上[l,r]的最大值 = max( f[l][p] , f[x][R] );
代码:
cin>>Q;
for (i=1;i<=Q;i++)
{
cin>>L>>R;
int len=R-L+1;
int p=(int)(log(len)/log(2));
x=R-bit[p]+1;
cout<<max(f[L][p],f[x][p])<<endl;
}
针对这个板子,完整代码如下(之前的bit[i]统一改成位运算<注意位运算与加减乘除优先级>):
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int f[100001][40],a,x,LC,n,m,p,len,l,r;
int main()
{
scanf("%d%d",&n,&m);
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]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
p=(int)(log(r-l+1)/log(2));
printf("%d
",max(f[l][p],f[r-(1<<p)+1][p]));
}
return 0;
}