线段树-求最大值-python|c++

题目描述

现在请求你维护一个数列,要求提供以下两种操作:

1、 查询操作。

语法:Q L

功能:查询当前数列中末尾 LL 个数中的最大的数,并输出这个数的值。

限制: L 不超过当前数列的长度。(L>0)

2、 插入操作。

语法:A n

功能:将 n 加上 t,其中 t 是最近一次查询操作的答案(如果还未执行过查询操作,则 t=0),并将所得结果对一个固定的常数 D 取模,将所得答案插入到数列的末尾。

限制:n 是整数(可能为负数)并且在长整范围内。

注意:初始时数列是空的,没有一个数。

输入描述

第一行两个整数,M 和 D,其中 M 表示操作的个数,D 如上文中所述。

接下来的 M 行,每行一个字符串,描述一个具体的操作。语法如上文所述。

其中,1≤M≤2×10^5,1≤D≤2×10^9。

输出描述

对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。

输入输出样例

示例 1

输入

5 100
A 96
Q 1
A 97
Q 1
Q 2

输出

96
93
96

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:

线段树原理

我们以 {1,4,5,8,6,2,3,9,10,7​​​}为例,讲解线段树的原理。首先用一颗满二叉树实现线段树,用于查询任意子区间的最小值。如图

 

每个结点上圆圈内的数字是这棵子树的最小值。圆圈旁边的数字,例如根结点的"1:[1,10]​​",1​ 表示结点的编号,[1,10]​ 是这个结点代表的元素范围,即第 1 到第 10 个元素

那我们来说说查询任意区间 [i, j]的最小值。例如查区间 [4,9] 的最小值,递归查询到区间 [4, 5]、[6,8]、[9,9],见图中画横线的线段,得最小值min{6, 2, 10} = 2

我们编码时可以使用标准的二叉树数据结构。

用数组 tree[] 实现一棵满二叉树。每个结点 x 的左右儿子是:

  • 左儿子:p<<1,即p×2。例如,根结点 tree[1]的左儿子是tree[2],结点 tree[12] 的左儿子是 tree[24]
  • 右儿子:p<<1|1,即 p×2+1。例如根结点tree[1] 的右儿子是tree[3],结点tree[12] 的左儿子是 tree[25]。 

当有 N个数时,需要把二叉树的空间开到  4N 大,假设有一棵处理 n 个元素(叶子结点有 n 个)的线段树,且它的最后一层只有 1 个叶子,其他层都是满的;如果用满二叉树表示,它的结点总数是:最后一层有 2n 个结点(其中2n−1 个都浪费了没用到),而前面所有的层有 2n 个结点,加起来共 4n 个结点。

具体题目

回到本题中,我们以python代码为例子讲解。设计三个函数来实现线段是的功能:

1. build(p,l,r):

我们通过此函数建立一颗空树,其中p是tr[p],它代表区间【l,r】。buiild()是一个递归函数,递归到最底的叶子结点,赋初始值tree[p] = 0。建树用二分法,从根结点开始逐层二分到叶子结点。此外我们通过下面这行代码,实现了从底往上的值的返回

 tr[p].sum=max(tr[p<<1].sum,tr[p<<1|1].sum)

2.modify(x,y,z,p,l,r):

我们通过这个函数实现区间【x,y】的更新,p​ 表示结点tr[p],l​ 是左子树,r 是右子树。在这道题中我们使得[x,y]为【cnt,cnt】从而实现了对新增结点的赋值为(z+t)

3.query(x,y,p,l,r):

查询函数,查找区间【x,y】内的最大值,会有四种情况:

  • 如果这棵子树完全被 [L, R]覆盖,也就是说这棵子树在要查询的区间之内,那么直接返回 tree[p]的值。见下列代码的 3- 4 行。这一步体现了线段树的高效率。
  • 如果不能覆盖,那么需要把这棵子树二分,再继续下面两步的查询。
  • 如果 L 与左部分有重叠。查询【l,mid】部分
  • 如果 R与右部分右重叠。查询【mid+1,r】部分

 query也是递归函数

注:python代码中的函数上面的注释说明的其参数和c++参数的对应关系

c++代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 200001;
const int INF = 0X7FFFFFFF;

int ls(int p){ return p<<1;  }     //左儿子,编号是 p*2
int rs(int p){ return p<<1|1;}     //右儿子,编号是 p*2+1

int tree[N<<2];          //4倍空间

void push_up(int p){                           //从下往上传递区间值
    //tree[p] = tree[ls(p)] + tree[rs(p)];      //区间和
    tree[p] = max(tree[ls(p)], tree[rs(p)]);    //区间最大值
}
void build(int p,int pl,int pr){      //结点编号p指向区间[pl, pr]
    if(pl==pr){                     //到达最底层的叶子,存叶子的值
        tree[p] = -INF;
        return;
    }
    int mid = (pl+pr) >> 1;                //分治:折半
    build(ls(p),pl,mid);                   //递归左儿子
    build(rs(p),mid+1,pr);                 //递归右儿子
    push_up(p);                            //从下往上传递区间值
}

void update(int p,int pl,int pr,int L,int R,int d){   
                 //区间修改,更新[L, R]内最大值
    if(L<=pl && pr<=R){              
                //完全覆盖,直接返回这个结点,它的子树不用再深入了
        tree[p] = d;
        return;
    }
    int mid=(pl+pr)>>1;
    if(L<=mid) update(ls(p),pl,mid,L,R,d);    //递归左子树
    if(R>mid)  update(rs(p),mid+1,pr,L,R,d);  //递归右子树
    push_up(p);                               //更新
    return;
}

int query(int p,int pl,int pr,int L,int R){ //在查询区间[L, R]的最大值
    int res = -INF;
    if (L<=pl && pr<=R)
        return tree[p];                                 //完全覆盖
    int mid=(pl+pr)>>1;
    if (L<=mid) res = max(res, query(ls(p),pl,mid,L,R)); 
                 //L与左子结点有重叠
    if (R>mid)  res = max(res, query(rs(p),mid+1,pr,L,R));  
                 //R与右子结点有重叠
    return res;
}
int main (){
    int t=0,cnt=0,m,D;
    scanf ("%d%d",&m,&D);
    build(1,1,N);          //这样写也行:   update(1,1,N,1,N,-INF);
    for (int b=1;b<=m;++b){
        char c[2];int x;
        scanf ("%s %d",c,&x);
        if (c[0]=='A'){
            cnt++;
            update(1,1,N,cnt,cnt,(x+t)%D);
          //update(1,1,N,cnt,(x+t)%D);
        }
        else {
            t = query(1,1,N,cnt-x+1,cnt);
            printf ("%d\n",t);
        }
    }
    return 0;
}

python代码

N=100001
class SegTree():
    def __init__(self):
        self.sum=0

tr=[SegTree() for i in range(N<<2)]

def build(p,l,r):
    if l==r:
        tr[p].sum=0
        return
    mid=(l+r)>>1
    build(p<<1,l,mid)
    build(p<<1|1,mid+1,r)
    tr[p].sum=max(tr[p<<1].sum,tr[p<<1|1].sum)

#x=L,y=R,z=d,l=pl,r=pr
def modify(x,y,z,p,l,r):
    if x<=l and r<=y:
        tr[p].sum=z
        return
    mid=(r+l)>>1
    if x<=mid:
        modify(x,y,z,p<<1,l,mid)
    if y>mid:
        modify(x,y,z,p<<1|1,mid+1,r)
    tr[p].sum=max(tr[p<<1].sum,tr[p<<1|1].sum)
    return

#x=L,y=R,l=pl,r=pr
def query(x,y,p,l,r):
    res=-1000
    if x<=l and r<=y:
        return tr[p].sum
    mid=(l+r)>>1
    if x<=mid:
        res=max(res,query(x,y,p<<1,l,mid))
    if y>mid:
        res=max(res,query(x,y,p<<1|1,mid+1,r))
    return res


[m,D]=list(map(int,input().split()))
build(1,1,N)
cnt=0
t=0
while m>0:
    m-=1
    op=list(input().split())
    if op[0]=='A':
        cnt+=1
        modify(cnt,cnt,(int(op[1])+t)%D,1,1,N)
    if op[0]=='Q':
        t=query(cnt-int(op[1])+1,cnt,1,1,N)
        print(t)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

晓宜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值