RMQ

Posted yiyiyizqy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RMQ相关的知识,希望对你有一定的参考价值。

总结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;
}

以上是关于RMQ的主要内容,如果未能解决你的问题,请参考以下文章

51nod1174(RMQ)

[luoguP1816] 忠诚(RMQ || 线段树)

Balanced Lineup(RMQ)

RMQ入门

RMQ ST表

AcWing 1270. 数列区间最大值(RMQ问题)