P1198 [JSOI2008]最大数(用splay)

本文介绍了一道名为“最大数”的JSOI2008竞赛题,通过对数列进行查询和插入操作来求解最大值。文章详细讲解了使用Splay树实现的算法流程及代码细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P1198 [JSOI2008]最大数

题目描述

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

1、 查询操作。

语法:Q L

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

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

2、 插入操作。

语法:A n

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

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

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

输入输出格式

输入格式:

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

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

输出格式:

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

输入输出样例

输入样例#1: 复制

5 100

A 96

Q 1

A 97

Q 1

Q 2

输出样例#1: 复制

96

93

96

代码分析:

#include <iostream>

#include <string.h>

#include <stdio.h>

#include <algorithm>

using namespace std;

const int INF=0x3f3f3f3f;

const int mm=200010;

int d;

struct node

{

    int fa;

    int son[2];

    long long val;

    int sz;

    long long mx;///当前节点及其子节点中的最大值

}tree[mm];

int root,tot1;///tot1当前节点总数

int s[mm],tot2;///因删除而空闲的节点

int n;

long long q;

#define get(x) (tree[tree[x].fa].son[1]==(x))

#define Key_value tree[tree[root].son[1]].son[0]

#define t tree[x]

#define lson tree[x].son[0]

#define rson tree[x].son[1]

#define tlson tree[tree[x].son[0]]

#define trson tree[tree[x].son[1]]

 

void PushUp(int x)

{

    t.sz=tlson.sz+trson.sz+1;

    t.mx=max(t.val,tlson.mx);

    t.mx=max(t.mx,trson.mx);

}

inline void Rotate(int x)

{

    int which=get(x);

    int y=tree[x].fa;

    int z=tree[y].fa;

    tree[y].son[which]=tree[x].son[which^1];

    tree[tree[y].son[which]].fa =y;

    tree[x].son[which^1]=y;

    tree[y].fa=x;

    tree[x].fa=z;

    if(z!=0)

    {

        tree[z].son[tree[z].son[1]==y]=x;

    }

    PushUp(y);

    PushUp(x);

}

inline void Splay(int x,int goal)///将当前节点转到目标goal之下,例如goal=0,则表示转为根

{

    int fa=tree[x].fa;

    while(fa!=goal)

    {

        if(tree[fa].fa!=goal)

            Rotate(get(x)==get(fa)?fa:x);

        Rotate(x);

        fa=tree[x].fa;

    }

    if(goal==0)root=x;

}

inline int Get_Kth(int x,int k)///得到当前节点下第k个数据在tree中的位置

{

    //PushDown(x);

    int tt=tlson.sz+1;

    if(tt==k)return x;

    if(tt>k)return Get_Kth(lson,k);

    else return Get_Kth(rson,k-tt);

}

/***初始化***/

void NewNode(int &x,int fa,long long v)///在节点father下新加一个值为v的节点,x返回该节点在tree数组中的位置

{

    if(tot2)x=s[tot2--];

    else x=++tot1;

    ///node s=tree[x];不能

    t.fa=fa;

    lson=rson=0;

    t.val=t.mx=v;

    t.sz=1;

}

void Init()

{

    root=tot1=tot2=0;

NewNode(root,0, -1);

NewNode(tree[root].son[1],root, -1);

PushUp(root);

}

void Insert_test(long long v)

{

    Splay(2,root);

    int now=Key_value;

    while(tree[now].son[1]!=0)

    {

        now=tree[now].son[1];

    }

    NewNode(tree[now].son[1],now,v);

    int push=now;

    while(push!=0)

    {

        PushUp(push);

        push=tree[push].fa;

    }

}/*先是写的这个,没有多想变量之间的关系,但是后来发现其实这个太复杂。可以用tot1(总节点数)直接秒掉这个操作。*/

void Insert(long long v)

{

    int x=(tot1==2)?1:tot1;

    Splay(x,0);

    NewNode(Key_value,2,v);

    PushUp(2);

    PushUp(root);

}/*我们可以利用tot1,也就是总节点数,因为,我们发现tot1其实就是最后一个的编号(除了开始插入的tree[2]);这比之前写的那个简单多了。。。*/

 

long long Get_Max(int l)

{

    Splay(Get_Kth(root,l-1),0);

    Splay(2,root);

    return tree[Key_value].mx;

}/*我们把[L,R]之间的数做处时,其实都可以用这种方法:

先把L-1这个节点splayroot

接着把R+1这个节点splaytree[root].son[1]root的右子树)。

然后我们就可以发现在l-1r+1这两个节点中间,夹着一棵树,这棵树包含了我们要的区间[L,R]

最后再在这棵树上操作就可以了。

 

这里我们也是用这种方法。

只是R是开始的tree[2]而已,方法还是这样:

计算出所谓的r=l-1(注意:这里的l不是题干中的l,在主函数中我们把l处理成了L的树中的编号)

tree[2],splay上去。

再处理中间夹着的树就可以了。

 

*/

 

int main()

{

    scanf("%d%d",&n,&d);

        Init();

        char op[20];

        long long x;

        while(n--)

        {

            scanf("%s",op);

            if(op[0]=='A')

            {

                scanf("%lld",&x);

                x=(x+q)%d;

                x+=d;

                x%=d;

                Insert(x);

            }

            else

            {

                scanf("%d",&x);

                x=tree[root].sz-x;///x处理成L的树中的编号。

printf("%lld\n",q=Get_Max(x));

            }

        }

 

    return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值