洛谷P3369 【模板】普通平衡树(Treap/SBT)


平衡树,一种其妙的数据结构


题目传送门

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

插入x数

删除x数(若有多个相同的数,因只删除一个)

查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)

查询排名为x的数

求x的前驱(前驱定义为小于x,且最大的数)

求x的后继(后继定义为大于x,且最小的数)

输入输出格式

输入格式:

第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1≤opt≤6 )

输出格式:

对于操作3,4,5,6每行输出一个数,表示对应答案

输入样例

10

1 106465

4 1

1 317721

1 460929

1 644985

1 84185

1 89851

6 81968

1 492737

5 493598

输出样例

106465

84185

492737

说明

时空限制:1000ms,128M

1.n的数据范围:100000 n≤100000

2.每个数的数据范围: [-10^7——10^7]


注释什么的都写代码里好了

小伙伴们在洛谷上提交不要选c++11

神奇RE和编译失败

如果有dalao看出为什么的话就教教我呗QAQ

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

int n,ans;
struct node
{
    node* ch[2];//左右孩子指针,0为左孩子,1,为右孩子
    int v,r;//v为该节点权值;r为优先级
    int sum,cnt;//sum表示该节点的子树的节点数,cnt是该节点保存相同权值的个数 

    node(int v):v(v){ sum=cnt=1; r=rand(); ch[0]=ch[1]=NULL;}//构造函数,用于初始化

    int cmp(int x) const{ if(x==v) return -1; return x<v ?0:1; }
    //这里的这个成员函数将在下面解释
    void update()
    {
        sum=cnt;
        if(ch[0]!=NULL) sum+=ch[0]->sum;
        if(ch[1]!=NULL) sum+=ch[1]->sum;
    }//更新新函数,在旋转时调用
};
node* rt=NULL;//这个是初始的根

//d=0代表左旋;d=1代表右旋
void rotate(node* &p,int d)//结点记得加引用
{
    node* k=p->ch[d^1];
    p->ch[d^1]=k->ch[d]; //改变k的子树的位置
    k->ch[d]=p;//将k旋转至p上方
    p->update();
    k->update();
    //旋转后要跟新节点信息
    p=k;
    //将旋转上去的k节点作为当前子树新的根节点
}

void ins(node* &p,int x)//x为带插入权值,结点记得加引用
{
    if(p==NULL){ p=new node(x); return; }
    //如果结点为NULL ,则找到了带插入结点,进行初始化
    if(p->v==x){ p->cnt++; p->sum++; return; }
    //如果已有该结点,则cnt++
    int d=p->cmp(x);
    //这里运用了结构体中的cmp函数,用以确定x该插入左孩子还是右孩子
    //若x<该结点权值,cmp返回0,插入左孩子;反之亦然
    ins(p->ch[d],x);
    //递归插入
    if( p->ch[d]->r < p->r ) rotate(p,d^1);
    //*划重点*;插入后判断优先级以保证堆的性质
    p->update();
    //插入后要更新信息
}

void del(node* &p,int x){
    if(p==NULL) return;//结点记得加引用
    if(x==p->v)//找到待删除结点
    {
        if(p->cnt>1){ p->sum--; p->cnt--; return;}
        //如果该节点记录个数大于一,则直接--,不用删除
        else
        {
            if(p->ch[0]==NULL)
            {node *k=p; p=p->ch[1]; delete(k); }
            else if(p->ch[1]==NULL)
            {node *k=p; p=p->ch[0]; delete(k); }
            //如果这个结点只有一棵子树,就以该子树代替该节点

            else//如果两棵子树都不为空
            {
                //先把优先级较高的子树旋转到根
                //然后递归再另一颗子树中删除p
                int dd=p->ch[0]->r < p->ch[1]->r ?1 :0;
                rotate(p,dd); del(p->ch[dd],x);
            }
        }
    }

    //递归寻找待删除结点
    else if(x < p->v)del(p->ch[0],x);
    else del(p->ch[1],x);
    if(p!=NULL)p->update();
    //删除后一定要更新信息,更新前判断p不为NULL
}

int rank(node* p,int x)//查询函数都不加引用!!!
{
    int sum=0;
    if(p->ch[0]!=NULL) sum=p->ch[0]->sum;
    if(x<p->v) return rank(p->ch[0],x);
    //向下寻找待查询结点
    if(x==p->v) return sum+1;
    //找到待查询结点,则其排名为左子树节点数+1
    else return sum+p->cnt+rank(p->ch[1],x);
    //待查询权值在右子树内
    //则该节点即其左子树必定小于待查询结点
    //所以加上该节点左孩子的sum+改点cnt,递归右孩子
}

int kth(node* p,int x)//查询函数都不加引用!!!
{
    int sum=0;
    if(p->ch[0]!=NULL) sum=p->ch[0]->sum;
    if(x<=sum) return kth(p->ch[0],x);
    //若待查询排名小于左孩子数,则应继续在左子树递归寻找
    else if(x<=sum+p->cnt) return p->v;
    //如果排名小于左孩子数加该节点cnt,则该节点为待查询结点
    else return kth(p->ch[1],x-sum-p->cnt);
    //如果排名还要大,那就要往右子树找
}

void pre(node* p,int x)//查询函数都不加引用!!!
{
    if(p==NULL) return;
    if( x > p->v ){ ans=p->v; pre(p->ch[1],x);}
    //找到小于x的数,但不一定最大,所以找右子树
    else pre(p->ch[0],x);
    //如果小于等于的话就找左子树啰
}

void nxt(node* p,int x)//查询函数都不加引用!!!
{
    //就是和找前驱正好相反啦
    if(p==NULL) return ;
    if( x < p->v ){ ans=p->v; nxt(p->ch[0],x);}
    else nxt(p->ch[1],x);
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        int k=read(),x=read();
        if(k==1) ins(rt,x);
        else if(k==2) del(rt,x);
        else if(k==3) cout<<rank(rt,x)<<endl;
        else if(k==4) cout<<kth(rt,x)<<endl;
        else if(k==5) { pre(rt,x); cout<<ans<<endl; }
        else if(k==6) { nxt(rt,x); cout<<ans<<endl; }
    }
    return 0;
}

给大家看一下蒟蒻的悲惨经历QAQ

一道题; 一百五十行;

两天; 六个小时;

千万个指针; 亿万次调试;

8次提交 ;

终于——

稻花香里说RE,听取 WA声一片;

指针,已使我目不忍视了;旋转,尤使我耳不忍闻;

然而正真的猛士,敢于直面惨淡的AC率,敢于正视淋漓的数据结构;

这是怎样的AC者与OIer!!!

仅使在Treap和SBT的世界中得以暂且偷生;

我不知道平衡树的世界何时才是一个尽头!!

忘却的伸展树快要降临了罢;

我正有学一下Splay的必要了;

沉默呵,沉默呵,不在爆零中灭亡,就AK中爆发;

                                        ——《纪念AC君》

洛谷P3369 【模板】普通平衡树(Treap/SBT)的更多相关文章

  1. luoguP3369[模板]普通平衡树(Treap/SBT) 题解

    链接一下题目:luoguP3369[模板]普通平衡树(Treap/SBT) 平衡树解析 #include<iostream> #include<cstdlib> #includ ...

  2. 洛谷.3369.[模板]普通平衡树(fhq Treap)

    题目链接 第一次(2017.12.24): #include<cstdio> #include<cctype> #include<algorithm> //#def ...

  3. 【洛谷P3369】普通平衡树——Splay学习笔记(一)

    二叉搜索树(二叉排序树) 概念:一棵树,若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值: 它的左.右子树也分别为二叉搜索树 ...

  4. 洛谷.3369.[模板]普通平衡树(Splay)

    题目链接 第一次写(2017.11.7): #include<cstdio> #include<cctype> using namespace std; const int N ...

  5. 洛谷.3391.[模板]文艺平衡树(Splay)

    题目链接 //注意建树 #include<cstdio> #include<algorithm> const int N=1e5+5; //using std::swap; i ...

  6. 【洛谷P3369】【模板】普通平衡树题解

    [洛谷P3369][模板]普通平衡树题解 题目链接 题意: 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3 ...

  7. 洛谷P3369普通平衡树(Treap)

    题目传送门 转载自https://www.cnblogs.com/fengzhiyuan/articles/7994428.html,转载请注明出处 Treap 简介 Treap 是一种二叉查找树.它 ...

  8. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  9. AC日记——【模板】普通平衡树(Treap/SBT) 洛谷 P3369

    [模板]普通平衡树(Treap/SBT) 思路: 劳资敲了一个多星期: 劳资终于a了: 劳资一直不a是因为一个小错误: 劳资最后看的模板: 劳资现在很愤怒: 劳资不想谈思路!!! 来,上代码: #in ...

随机推荐

  1. Putty(菩提)远程连接服务器教程听语音

    Putty是一款优秀的免费串行接口连接软件,由于其绿色和性能深受业界好评,绿色是指putty使用便捷只需要将putty下载到电脑,无需安装,只需要在电脑上新建一个快捷方式就可以使用.出色的性能是指pu ...

  2. 邓_PHP面试【001】

    1.双引号和单引号的区别 双引号解释变量,单引号不解释变量 双引号里插入单引号,其中单引号里如果有变量的话,变量解释 双引号的变量名后面必须要有一个非数字.字母.下划线的特殊字符,或者用{}讲变量括起 ...

  3. Git学习(2)-使用Git 代码将本地文件提交到 GitHub

    上次随笔写到git的安装和运用命令窗口创建本地版本库,这次主要讲一下用git代码将本地文件提交到GitHub上. 前提是有一个GitHub账号. 1.创建一个新的版本库,进入到你本地项目的根目录下(我 ...

  4. jquery mobile-按钮控件

    jQuery Mobile 中的按钮会自动获得样式,这增强了他们在移动设备上的交互性和可用性.我们推荐您使用 data-role="button" 的 <a> 元素来创 ...

  5. Effective Java 第三版——27. 消除非检查警告

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  6. SVN的安装和配置

    SVN为程序开发团队常用的代码管理,版本控制软件:下面我们来介绍TortoiseSVN的安装,和其服务器的搭建:(下面为windows 64位系统下的搭建) 闲来无事,就在本地搭建了一个SVN环境,网 ...

  7. linux运维学习

    export 和unset 设置和取消变量 echo 的双引号和单引号的区别:双引号里的会被替换,单引号里的都会直接输出.

  8. MS SQL 批量给存储过程/函数授权

    在工作当中遇到一个类似这样的问题:要对数据库账户的权限进行清理.设置,其中有一个用户Test,只能拥有数据库MyAssistant的DML(更新.插入.删除等)操作权限,另外拥有执行数据库存储过程.函 ...

  9. redis数据类型-有序集合

    有序集合类型 在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入.删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素.获得指 ...

  10. Hibernate的五个主要接口

    Hibernate作为持久成中间件,它的具体实现对与上层调用是透明的,即上层通过接口来调用Hibernate的具体实现,所以对于入门级别的讨论来说,自然应该先从接口开始了.