题目描述
现在请求你维护一个数列,要求提供以下两种操作:
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)