总结rmq
Rmq的功能是寻找一个区间里面最大值或者最小值。
求一个区间里面最大的数可以怎么求呢。可以两个两个做比较,求出最大(小)值,从两组(四个数)之中挑取出刚刚两个数求出的最大值,再比较,就是四个数字的最大值,然后再到八个。
这样说有点抽象,下面列一个表格
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
2^0 |
3 |
5 |
4 |
1 |
7 |
9 |
6 |
8 |
10 |
2^1 |
5 |
5 |
4 |
7 |
9 |
9 |
8 |
10 |
10 |
2^2 |
5 |
7 |
9 |
9 |
9 |
10 |
10 |
10 |
10 |
左边第一列,是说,以i开头的2^0个数字作比较后,最大的数字是多少。
我们能从第二和第三行发现,比较时,只要取出i上一层的值,和上一层加2^(L-1)个数字,一比较,既是2^L中的最大值。
因此可以用f[i][L]数组记录,从第i个为起点,连续2^L个数的最大值。所以,可以得出规律,f[i][L]=max(f[i][l-1],f[i+2^(L-1)][L-1])。但是要注意,编代码时不可能写成2^L的格式,应该用1<<L。
求最小值时,把max改成min就可以了。
但是,我们这样只求出了个数为1、2、4、8、16……时的最大(小)值,那么如果是单数时怎么办呢,我当时的第一反应是补,但是事实证明,还有更优的方法。
当给出7个数字:1 3 5 7 9 11 13 时,我们无妨可以把它分为四个一组,1 3 5 7一组,7 9 11 13一组,这样子就可以不漏掉。如果是分为两个一组呢?那是不够的。
I |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
K |
1 |
1 |
2 |
2 |
4 |
4 |
4 |
8 |
8 |
P |
0 |
0 |
1 |
1 |
2 |
2 |
2 |
3 |
3 |
第一行记录的是个数为i时。第二行是所需要拆分成的组数,最后一行是把第二行的组数转化为2^x组中的x,可以节省很多数组空间,上面的f数组中的L也作了同样的优化。
接下来,找规律的时候就到了。我们会发现,当前面第i-1个的K*2小于i时,ki=ki-1*2。也就是pi=pi-1++。算出p也就是做好预处理。
最后的答案
long=b-a+1; ma=max(f[a][p[long]],f[b-2^k+1][k])。
int main()
{
freopen("1656.in","r",stdin);
freopen("1656.out","w",stdout);
cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>a;
f[i][0]=a;
g[i][0]=a;
}
p[1]=0;
for(int i=2;i<=n;i++)
{
if((1<<p[i-1]+1)<i) p[i]=p[i-1]+1;
else p[i]=p[i-1];
}
for(int l=1;(1<<l)<n;l++)
{
for(int i=1;i<=n;i++)
{
f[i][l]=f[i][l-1];
g[i][l]=g[i][l-1];
if(i+(1<<(l-1))<=n)
{
f[i][l]=max(f[i][l],f[i+(1<<(l-1))][l-1]);
g[i][l]=min(g[i][l],g[i+(1<<(l-1))][l-1]);
}
}
}
while(q)
{
q--;
cin>>a>>b;
int lo=b-a+1;
int k=p[lo];
int mi=min(g[a][k],g[b-(1<<k)+1][k]);
int ma=max(f[a][k],f[b-(1<<k)+1][k]);
int ans=ma-mi;
cout<<ans<<endl;
}
return 0;
}