数列区间最大值-RMQ/线段树/树状数组

题目

输入一串数字,给你 M 个询问,每次询问就给你两个数字 X,Y,要求你说出 X 到 Y 这段区间内的最大数。

输入格式
第一行两个整数 N,M 表示数字的个数和要询问的次数;

接下来一行为 N 个数;

接下来 M 行,每行都有两个整数 X,Y。

输出格式
输出共 M 行,每行输出一个数。

数据范围
1 ≤ N ≤ 1 0 5 1≤N≤10^5 1N105,
1 ≤ M ≤ 1 0 6 1≤M≤10^6 1M106,
1 ≤ X ≤ Y ≤ N 1≤X≤Y≤N 1XYN,
数列中的数字均不超过 2 31 − 1 2^{31}−1 2311

Solution#1 RMQ

RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干次询问RMQ(i,j),返回数列A中下标在区间[i,j]中的最小/大值。

本文介绍一种比较高效的ST算法解决这个问题。ST(Sparse Table)算法可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

1)预处理

设A[i]是要求区间最值的数列,F[i, j]表示从第i个数起连续2^j个数中的最大值。(DP的状态)

例如:

A数列为:3 2 4 5 6 8 1 2 9 7

F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。同理 F[1,1] = max(3,2) = 3, F[1,2]=max(3,2,4,5) = 5,F[1,3] = max(3,2,4,5,6,8,1,2) = 8;

并且我们可以容易的看出F[i,0]就等于A[i]。(DP的初始值)

我们把F[i,j]平均分成两段(因为F[i,j]一定是偶数个数字),从 i 到i + 2 ^ (j - 1) - 1为一段,i + 2 ^ (j - 1)到i + 2 ^ j - 1为一段(长度都为2 ^ (j - 1))。于是我们得到了状态转移方程F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1])。

2)查询

假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可以重复,比如查询1,2,3,4,5,我们可以查询1234和2345)。

因为这个区间的长度为j - i + 1,所以我们可以取k=log2( j - i + 1),则有:RMQ(i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。

举例说明,要求区间[1,5]的最大值,k = log2(5 - 1 + 1)= 2,即求max(F[1, 2],F[5 - 2 ^ 2 + 1, 2])=max(F[1, 2],F[2, 2]);

原文链接

#include <cstdio>
#include <cmath>
using namespace std;
const int N = 1e5+5;

int n,m;
int a[N],f[N][20];

int rmq(int l,int r)
{
    int k=0;
    while((1<<(k+1))<=(r-l+1))
        k++;
    return max(f[l][k],f[r-(1<<k)+1][k]);
}

int main()
{
    scanf("%d%d", &n, &m);
    int x,y;
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        f[i][0] = a[i];
    }
    
    for(int j=1; (1<<j)<=n; 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]);
    
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",rmq(x,y));
    }
    
    return 0;
}

Solution#2 线段树

对常规线段树的修改:

  • 原本父节点是左右子树之和,这里修改为左右子树的最大值
  • 查询过程中,考虑负数情况,把原本返回0的地方修改返回最小值
  • 时间要求严格,要卡常数(用scanf和printf)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <climits>
using namespace std;
const int N = 1e5+5;

int a[N],t[4*N];

void bulid(int node,int start,int end)
{
    if(start==end)
        t[node]=a[start];
    else
    {
        int mid=start+end>>1;
        bulid(node<<1,start,mid);
        bulid(node<<1|1,mid+1,end);
        t[node]=max(t[node<<1],t[node<<1|1]);
    }
}

int query(int node,int start,int end,int l,int r)
{
    int min_inf=INT_MIN;
    if(end<l || start>r)
        return min_inf;
    else if(start==end)
        return t[node];
    else if(l<=start && end<=r)
        return t[node];
    else
    {
        int mid = start+end>>1;
        int left_max = max(min_inf,query(node<<1,start,mid,l,r));
        int right_max = max(min_inf,query(node<<1|1,mid+1,end,l,r));
        return max(left_max,right_max);
    }
}
int main()
{
    int n,m,x,y;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)  scanf("%d",&a[i]);
    bulid(1,1,n);
    while (m -- )
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",query(1,1,n,x,y));
    }
    return 0;
}

Solution#3 树状数组

暂时留个坑,因为对树状数组的原理了解不深,待深入学习后回来补充,把代码一步步跑完,发现树状数组是个很神奇的结构,把二进制玩活了。贴上代码,供感兴趣的各位学习。

#include <bits/stdc++.h>
using namespace std;
const int N = 15;
int nums[N], bit[N], n, m;

inline int lowbit(int x) {
    return x & -x;
}

void build() { // 初始化树状数组
    for (int i = 1; i <= n; ++ i) {
        bit[i] = nums[i];
        for (int j = 1; j < lowbit(i); j <<= 1)
            bit[i] = max(bit[i], bit[i - j]);
    }
}

int query(int l, int r) { // 区间查询
    int maxv = INT_MIN;
    while (l <= r) {
        maxv = max(maxv, nums[r]);
        -- r;
        for (; l <= r - lowbit(r); r -= lowbit(r))
            maxv = max(maxv, bit[r]);
    }
    return maxv;
}

int main()
{
    int l, r;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++ i) scanf("%d", &nums[i]);
    build();
    while (m --) {
        scanf("%d %d", &l, &r);
        printf("%d\n", query(l, r));
    }
    return 0;
}
/*from:https://www.acwing.com/solution/content/83790/*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linengcs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值