1012: [JSOI2008]最大数maxnumber

Time Limit: 3 Sec   Memory Limit: 162 MB

Submit: 2286  

Solved: 1041

[

​Submit​​][

​Status​​][

​Discuss​​]

Description

现在请求你维护一个数列,要求提供以下两种操作:1、 查询操作。语法:Q L功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。限制:L不超过当前数列的长度。2、 插入操作。语法:A n功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列的末尾。限制:n是非负整数并且在长整范围内。注意:初始时数列是空的,没有一个数。

Input

第一行两个整数,M和D,其中M表示操作的个数(M <= 200,000),D如上文中所述,满足(0

Output

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

Sample Input

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

Sample Output

96


93


96

评测链接:​​http://www.lydsy.com/JudgeOnline/problem.php?id=1012​


解法3:单调队列+二分查找(即本篇所讲);

用a[i]记录第i号点的值,用一个单调递减序列q[](我是从左到右递减)存储从1到n号点所有大于等于a[n]的节点的编号。

对于一个插入操作,假如是插入x,用二分查找找到从左到右第一个<=x的节点的编号j,将q[j]赋值为x的节点编号,并把j后面的元素都删掉(可以用len记录q数组元素总数,把len赋值为j就可以完成这个操作了)

对于一个查询操作Q L,用二分查找找到从左到右第一个>=n-l+1(n为当前节点总数)的点j,然后输出a[q[j]]即可。

(有疑问的欢迎留言)

代码:

#include<cstdio>
#define maxn (200000+100)
using namespace std;
//q记录单调递减队列 a记录节点值
// n记录节点总数 len记录单调队列中的元素总数
int q[maxn],a[maxn],n=0,len=0;

void init()
{
freopen("maxnumber.in","r",stdin);
freopen("maxnumber.out","w",stdout);
}
//将a[k]=x插入单调队列
inline void add(int x,int k)
{
if(x<a[q[len]]){q[++len]=k;return;}
int l=1,r=len,m;
while(l<=r)
{
m=(l+r)>>1;
if(x==a[q[m]]){len=m;return;}
if(x>a[q[m]])r=m-1;
else l=m+1;
}
//找到从左到右第一个<=x的节点的编号L,将q[L]赋值为k(x的节点编号),并将len赋为L,
len=l;q[len]=k;
}

inline int get(int k)
{
if(len==1)return a[n];
int l=1,r=len,m;
while(l<=r)
{
m=(l+r)>>1;
if(q[m]==k)return a[k];
if(k>q[m])l=m+1;
else r=m-1;
}
//找到从左往右第一个节点编号>=k的点L,a[q[l]]即为从k到n的最大值
return a[q[l]];
}

void work()
{
int m,d,t=0,i,b,k;
char s;
scanf("%d%d\n",&m,&d);
for(i=1;i<=m;i++)
{
scanf("%c%d\n",&s,&b);
if(s=='A')
{
a[++n]=(b+t)%d;
add(a[n],n);
}
else
printf("%d\n",t=get(n-b+1));
}
}

int main()
{
init();
work();
return 0;
}