【C++天梯计划】1.14 区间最值算法(RMQ)

🎆🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎆
今天我要开启一个新计划----【C++天梯计划】
目的是通过天梯计划,通过题目和知识点串联的方式,完成C++复习与巩固。

什么是RMQ?

RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。

ST算法

来看一下ST算法是怎么实现的(以最大值为例):
首先是预处理,用一个DP解决。设a是要求区间最值的数列,f[i,j]表示从第i个数起连续2^j个数中的最大值。例如数列3 2 4 5 6 8 1 2 9 7 ,f[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。f[1,2]=5,f[1,3]=8,f[2,0]=2,f[2,1]=4……从这里可以看出f[i,0]其实就等于a[i]。这样,DP的状态、初值都已经有了,剩下的就是状态转移方程。我们把f[i,j](j≥1)平均分成两段(因为j≥1时,f[i,j]一定是偶数个数字),从i到i+2(j-1)-1为一段,i+2(j-1)到i+2j-1为一段(长度都为2(j-1))。用上例说明,当i=1,j=3时就是3,2,4,5 和6,8,1,2这两段。f就是这两段的最大值中的最大值。于是我们得到了动规方程F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1])。
接下来是得出最值,也许你想不到计算出f有什么用处,一般要想计算max还是要O(logn),甚至O(n)。但有一个很好的办法,做到了O(1)。还是分开来。如在上例中我们要求区间[2,8]的最大值,就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2,2]和f[5,2]得到。扩展到一般情况,就是把区间[l,r]分成两个长度为2^n的区间(保证有f对应)。直接给出表达式:
k:=trunc(ln(r-l+1)/ln(2));
ans:=max(F[l,k],F[r-2^k+1,k]);
这样就计算了从l开始,长度为2k的区间和从r-2k+1开始长度为2^k的区间的最大值(表达式比较繁琐,细节问题如加1减1需要仔细考虑),二者中的较大者就是整个区间[l,r]上的最大值。

例题1:超级记忆力

题目描述

小A同学拥有无与伦比的超级记忆力,他可以一次性记住很多数字。
为了考验一下小A同学的记忆力,王老师一次性给小A展示了 NN 个整数。然后问了他 MM 个问题,每个问题给定一个区间,要求小A同学说出这个区间中的最大数是多少?
为方便老师检验小A同学的答案是否正确,请你先编程求出正确的答案。

输入

第一行两个整数 N,MN,M 表示数字的个数和要询问的次数;
接下来一行为 NN 个数;
接下来 MM 行,每行都有两个整数 X,YX,Y 表示询问的区间。
数据范围:
1≤N≤105,1≤M≤106,1≤X≤Y≤N1≤N≤10
5
,1≤M≤10
6
,1≤X≤Y≤N。数字不超过 C/C++ 的 int 范围。

输出

输出共 MM 行,每行输出一个数。

样例

输入
10 2
3 2 4 5 6 8 1 2 9 7
1 4
3 8
输出
5
8

代码:
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
 
long long input[100005];
 
struct Node
{
	int max, start, end;
	Node *left;
	Node *right;
	Node(int m, int s, int e)
	{
		max = m;
		start = s;
		end = e;
		left = right = NULL;
	}
};
 
int query(int l, int r, Node *root)
{
	if (root->start == l && root->end == r)
	{
		return root->max;
	}
 
	else if (l >= root->left->start && l <= root->left->end)
	{
		if (r <= root->left->end)
		{
			return query(l, r, root->left);
		}
		else
		{
			return max(query(l, root->left->end, root->left), query(root->right->start, r, root->right));
		}
	}
	else
	{
		return query(l, r, root->right);
	}
}
 
Node* build(int l, int r)
{
	if (l > r)
	{
		return NULL;
	}
 
	Node *p = new Node(0, l, r);
	if (l == r)
	{
		p->max = input[l];
		return p;
	}
 
	p->left = build(l, (l+r)/2);
	p->right = build((l+r)/2 + 1, r);
	p->max = max(p->left->max, p->right->max);
 
	return p;
}
long long n,q;
int main()
{

	cin>>n>>q;
	for (int i = 0; i < n; i++)
		scanf("%lld",&input[i]);
	Node *root = build(0, n-1);
	for (int i = 0; i < q; i++)
	{
	    long long l,r;
		scanf("%lld%lld",&l,&r);
		l--,r--;
		printf("%lld\n",query(l, r, root));
	}
    return 0;
}

例题2:连续k个数的最值

题目描述

给定 nn 个整数,求从第 11 个数到第 n-k+1n−k+1 个数为起点的每个数开始,连续 kk 个数的最大数和最小数。

输入

第 11 行有 22 个数 nn 和 kk 。
第 22 行有 nn 个整数,每个整数都在[-2[−23131\sim 2∼23131-1]−1]范围内。
1≤k≤n≤10^51≤k≤n≤10
5

输出

输出 n-k+1n−k+1 行,每行有 22 个整数,第 ii 行输出从第 ii 个数开始连续 kk 个数的最大值和最小值。

样例

输入
5 3
1 2 3 4 5
输出
3 1
4 2
5 3

代码:
#include<climits>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
int a[1000005], q[1000005],c[1000005];
int main()
{
	int n, k, i, j, h, t;
	scanf("%d%d", &n, &k);
	for (i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	h = 0; t = 0;
	for (i = 1; i <= n; i++)
	{
		while (h <= t && (i - k + 1 > q[h]))h++;//弹出队首
		while (h<=t&&(a[q[t]]>=a[i]))t--;//保证单调上升的序列
		q[++t] = i;//保存下标
		if (i >=k)c[i]=a[q[h]];
	}
	h = 0; t = 0;
	for (i = 1; i <= n; i++)
	{
		while (h<=t && (i - k + 1)>q[h])h++;
		while (h<=t&&(a[i] >= a[q[t]]))t--;
		q[++t] = i;
		if (i>=k)printf("%d %d\n",a[q[h]],c[i]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值