51Nod 1174:区间中最大的数

题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1174


区间最大值查询,挺简单的,原来学习了线段树,很容易可以解决这个问题,不过看到

别人用ST(Sparse Table)算法来解决这个问题,这个没学过,比较好奇就看了一下。

过程也挺简单的,就是动态规划的思想。有一个dp数组。


dp[i][j]    维护的是   【i  ,  i + 2^j - 1】这个区间的最大/最小值,

则dp[i][0]  维护的是【i, i + 2^0 -1】 = 【 i , i 】的最大/最小值。

因此dp[i][0] = a[i]

而dp[i][j]   = max/min【i , j】 = max/min(dp【 i , j-1】,dp【 i  + (1<<(j-1)) , j-1】


其意义如下图所示:

i ~ ( i + 2^j -1 )   中有2^j次方个数,我们可以把这整个区间分成2个拥有2^j-1次方个数的区间。


所以dp[i][j] 是由【 i , i + 2^(j-1)-1 】和 【i+2^(j-1),i+2^j-1-1】

第一个区间就是dp[i][j-1] ,第二个区间是dp[i+2^(j-1)][j-1]。

查询的时候,假如想要left,right的最大值或最小值,时间复杂度是0(1)。

我们知道left,right区间里面由right-left + 1.

而right不恰好是 i + 2^j - 1对应的数,因此这时我们求解的时候区间应该重叠。

我们找最大的k    2^k <= (right-left+1)     k = ceil(log(right-left+1))

则整个区间[left,right]可由两个区间 [left,left + 2^k -1]  和 [right-2^k+1][right]并起来构成

则区间的最大最小值就是  max/min(dp[left][k],dp[right-(1<<k)+1][k])得到。

对应如下图所示:



AC代码:ST算法

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>

using namespace std;

const int maxn = 10005;
int dp[maxn][32];
void init(int N)
{
    for(int i = 0; i < N; i++)
    {
        scanf("%d",&dp[i][0]);
    }
    for(int j = 1; j < 32; j++)
        for(int i = 0; i < N; i++)
        {
            if(i+(1<<(j-1))>=N) break;  ///越界
            dp[i][j] = max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
        }
}
int query(int left,int right)
{
    int k = (int)log2(right-left+1.0);
    int ans = max(dp[left][k],dp[right-(1<<k)+1][k]);
    return ans;
}
int main()
{
    int N,Q;
    while(~scanf("%d",&N))
    {
        init(N);
        scanf("%d",&Q);
        int i,j;
        while(Q--)
        {
            scanf("%d%d",&i,&j);
            printf("%d\n",query(i,j));
        }
    }
    return 0;
}


AC代码:线段树

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#define lchild left,mid,root<<1
#define rchild mid+1,right,root<<1|1

using namespace std;

const int maxn = 10005;
int Max[maxn<<2];
void push_up(int root)
{
    Max[root] = max(Max[root<<1],Max[root<<1|1]);
}
void build(int left,int right,int root)
{
    if(left == right)
    {
        scanf("%d",&Max[root]);
        return;
    }
    int mid = (left+right)>>1;
    build(lchild);
    build(rchild);
    push_up(root);
}
int query(int L,int R,int left,int right,int root)
{
    if(L<=left && right<=R)
        return Max[root];
    int mid = (left+right)>>1;
    int ans = 0;
    if(L <= mid) ans = max(ans,query(L,R,lchild));
    if(R > mid) ans = max(ans,query(L,R,rchild));
    return ans;
}
int main()
{
    int N,Q;
    while(~scanf("%d",&N))
    {
        memset(Max,0,sizeof(Max));
        build(1,N,1);
        scanf("%d",&Q);
        int i,j;
        while(Q--)
        {
            scanf("%d%d",&i,&j);
            int ans = query(i+1,j+1,1,N,1);
            printf("%d\n",ans);
        }
    }
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值