bzoj 1012 [JSOI2008]最大数maxnumber
Description
现在请求你维护一个数列,要求提供以下两种操作:
1、 查询操作。语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。限制:L不超过当前数列的长度。
2、 插入操作。语法:A n 功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列的末尾。限制:n是非负整数并且在长整范围内。
注意:初始时数列是空的,没有一个数。
Input
第一行两个整数,M和D,其中M表示操作的个数(M <= 200,000),D如上文中所
述
Output
对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。
Sample Input
5 100
A 96Q 1
A 97
Q 1
Q 2
Sample Output
96
93
96
题外话
每次打题面总是很痛苦……有没有哪位大犇有种既快又美观的输入题面的方法,蒟蒻在线求教……
题解
这题我知道很多大犇肯定是用许多高大上的算法做的,想法很神奇!。但是呢,像我这种小蒟蒻,做完这道题有别的收获,因为我使用线段树做的。网上有很多线段树的代码,基本上都是略过,因为这种暴力的蒟蒻想法其它博主不屑去讲。但是我看到却如获至宝,因为这题的线段树想法就是完美(?)的线段树插入操作模版!那么下面我就详细讲讲这题的暴力思想,顺带也给自己复习一下线段树的插入操作(以这题为背景)。
线段树的插入操作其实并没有我们想的那么难,在本题中,一开始这颗线段树是空的,但是我们也可以认为是一颗所有节点权值为0的线段树,那么根据题意,我们处理Q这个操作时,无非就是线段是的询问操作,接下来的关键是A操作。首先我们由题目的值,每个测点中我们最多只需要建一颗节点个数为M的线段树即可,为什么呢,因为每个测点中只有M次操作,也就是说假设生成数据的人脑子出了问题偏爱插入操作,所有操作都是插入操作,那也只有M个节点被插入了这颗线段树中。其实线段树的插入操作我们可以不妨换种思维,假设要将权值为v的节点插入到数列的末尾,无非就是相当于将线段树相应的节点的权值从0改成v,那么就好理解了,所以我们可以将线段树的插入操作看成是一种变形的修改操作,或者根本就是一种修改操作。
奉上代码
#include<cstdio>
#include<algorithm>
#define qmid(L,R) L+((R-L)>>1)
#define LL long long
using namespace std;
const int INFINITE=-4e18;
int n,tot;
LL st[4*200005],D;
inline int read_number(){
LL ret=0;char ch=getchar();
while (ch<'0'||ch>'9')ch=getchar();
while (ch>='0'&&ch<='9')ret=ret*10+ch-48,ch=getchar();
return ret;
}
inline char read_char(){
char ret=getchar();
while (ret=='\n'||ret==' ')ret=getchar();
return ret;
}
inline void insert(int root,int L,int R,int k,int v){ //插入操作
if (L==R){st[root]=v;return;} //如果到了叶子节点,那么就修改
int mid=qmid(L,R);
if (k<=mid)insert(root*2+1,L,mid,k,v);else insert(root*2+2,mid+1,R,k,v); //如果要插入的节点的位置小于mid那么就插入左儿子否则插入右儿子
st[root]=max(st[root*2+1],st[root*2+2]); //递归修改父亲节点
}
inline LL query(int root,int L,int R,int qL,int qR){ //询问操作
if (L>qR||R<qL)return INFINITE; //如果当前区间完全和要询问的区间搭不着边,那么就退无限小(也就是再怎样程序也不会取的的值)
if (L>=qL&&R<=qR)return st[root]; //如果当前区间包含在询问区间内,那么就退这个区间的极值
int mid=qmid(L,R);
return max(query(root*2+1,L,mid,qL,qR),query(root*2+2,mid+1,R,qL,qR)); //分别询问左儿子右儿子
}
int main(){
n=read_number(),D=read_number();
LL lst=0;
for (int i=1;i<=n;i++){
char ch=read_char();int x=read_number();
// putchar(ch),printf(" %d\n",x);
if (ch=='A')insert(1,1,n,++tot,(x+lst)%D);
else lst=query(1,1,n,tot-x+1,tot),printf("%lld\n",lst);
}
return 0;
}
/*
Sample Input
5 100
A 96
Q 1
A 97
Q 1
Q 2
*/
后记
这道题目我是将它当做线段树插入操作的经典题目来做的,当然网上还有另外的神奇算法,其中感觉比较简单的是单调栈,大家也可以去看看,毕竟题目不能AC了就了事了,还需要去看看他人的想法,这样才可以打通奇经八脉进一步提升自己。