treap:一种平衡的二叉搜索树

什么是treap(带旋)

treap=tree+heap,这大家都知道。因为二叉搜索树(BST)非常容易被卡成一条链而影响效率,所以我们需要一种更加平衡的树形结构,从而保持$O(logn)$的优秀复杂度。

那么为什么涉及到heap呢?我们知道因为堆有个非常好的性质,它的高度是$logn$的。于是就有人想,能不能够把二叉搜索树的优点与堆的优点结合起来呢?

接下去就要步入OI界的玄学基础————随机数了。

先不管均摊效率高低,我们把BST的每一个节点都赋值一个随机数,然后从两个值分别来看待这个树形结构:1.节点数值val;2.随机数值rnd。

我们把rnd作为关键字来维护一个堆,同时保持在以val为关键字下的二叉搜索树的性质。那么这样我们就有了一颗高度、查询为logn的二叉搜索树了。

至于为什么这样就可以保证在logn……似乎是可以用期望来保证均摊复杂度的优越的。

嘛,具体怎么样就要看人品了……

乍一看好像不可实现,那么让我们来看一些treap功能的代码段。

 struct node
{
int son[],tot,val,rnd;     //tot是以当前节点为根的子树大小,val是数值,rnd是随机值
int &operator [](const int a)
{
return son[a];        //son[0]左儿子节点,son[1]右儿子节点
}
}a[maxn];
 inline void insert(int &x, int p)  //在以x为根的子树中,插入p
{
if (!x){               //如果当前为空节点,则新建一个节点
x = ++cnt,a[cnt].tot = ;
a[cnt].val = p,a[cnt].rnd = randVal();
return;
}
a[x].tot++;
if (p <= a[x].val){        //由二叉搜索树的性质可得,如果p小于等于当前节点,那么应该插入到左子树
insert(a[x][], p);
if (a[a[x][]].rnd < a[x].rnd) rotate(x, );    //划重点!根据随机值来维护堆的结构!
}else{
insert(a[x][], p);
if (a[a[x][]].rnd < a[x].rnd) rotate(x, );
}
}

那么insert里的rotate是什么操作?

事实上,这就是treap——以及大部分平衡树的核心——旋转操作。

注意一下BST的特殊性质:左儿子小于等于自身;自身大于右儿子。

如果现在向左子树插入完毕之后,左儿子的rnd比自身要小了,按照堆的操作来说,应该将自身和左儿子互换才对。但是这样子就有可能不满足二叉搜索树的性质了。

不过我们可以把左儿子旋转上去,成为自身的祖先,仔细想想就会发现这种技巧同时保证了二叉搜索树和堆的性质。

以上就是rotate操作的全部。

上面这张图可以助于理解rotate;这里是rotate的代码。(我把左旋右旋写在一起了)

 inline int rotate(int &x, int p)
{
int t = a[x][p];
a[x][p] = a[t][^p],a[t][^p] = x;
change(x), change(t);
x = t;
}

emmm,还是挺优美的一种操作。

理解了treap的旋转操作之后,其他操作相对来说难度就不大了,我们来看一些板子吧。

参考资料:

1.平衡树讲解【Treap&&非旋Treap】

2.关于Treap学习总结

treap的题目

luoguP3369 【模板】普通平衡树(Treap/SBT)

题目描述

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

  1. 插入 xx 数
  2. 删除 xx 数(若有多个相同的数,因只删除一个)
  3. 查询 xx 数的排名(排名定义为比当前数小的数的个数 +1+1 。若有多个相同的数,因输出最小的排名)
  4. 查询排名为 xx 的数
  5. 求 xx 的前驱(前驱定义为小于 xx ,且最大的数)
  6. 求 xx 的后继(后继定义为大于 xx ,且最小的数)

输入输出格式

输入格式:

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

输出格式:

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

说明

时空限制:1000ms,128M

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

2.每个数的数据范围: [−10^7,10^7]


题目分析

这里就是treap(带旋)的所有操作,那么就直接上板子了。

 #include<bits/stdc++.h>
const int maxn = ; int n,cnt,root;
struct node
{
int son[],tot,val,rnd;
int &operator [](const int a)
{
return son[a];
}
}a[maxn]; int randVal()
{
static int seed = ;
return seed = int(seed*901832ll*seed%);
}
int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
inline void change(int x)              //为了维护子树大小tot。这个tot在以下的ask和find操作中有重要作用。
{
a[x].tot = a[a[x][]].tot+a[a[x][]].tot+;
}
inline int rotate(int &x, int p)          //旋转
{
int t = a[x][p];
a[x][p] = a[t][^p],a[t][^p] = x;
change(x), change(t);
x = t;
}
inline void insert(int &x, int p)          //插入
{
if (!x){
x = ++cnt,a[cnt].tot = ;
a[cnt].val = p,a[cnt].rnd = randVal();
return;
}
a[x].tot++;
if (p <= a[x].val){
insert(a[x][], p);
if (a[a[x][]].rnd < a[x].rnd) rotate(x, );
}else{
insert(a[x][], p);
if (a[a[x][]].rnd < a[x].rnd) rotate(x, );
}
}
void del(int &x, int p)                //删除
{
if (a[x].val == p){
if (a[x][]*a[x][]==){           //如果左儿子空或右儿子为空,那么使自身等于非空的那一个节点。
x = a[x][]+a[x][];
return;
}
if (a[a[x][]].rnd < a[a[x][]].rnd){   //依靠旋转操作保证堆结构,同时边旋转边删除节点
rotate(x, );
del(a[x][], p);
}else{
rotate(x, );
del(a[x][], p);
}
}else{
if (a[x].val > p) del(a[x][], p);
else del(a[x][], p);
}
change(x);
}
int find(int x, int p)                //查询以x为根的子树中,p的排名是多少
{
if (!x) return ;
if (a[x].val >= p) return find(a[x][], p);
return find(a[x][], p)+a[a[x][]].tot+;
}
int ask(int x, int p)                 //查询以x为根的子树中,排名为p的数是什么
{
if (a[a[x][]].tot == p-) return a[x].val;
if (a[a[x][]].tot >= p) return ask(a[x][], p);
return ask(a[x][], p-a[a[x][]].tot-);
}
int pre(int x, int p)                 //查询在以x为根的子树中,p的前驱是什么
{
if (!x) return -2e9;
if (a[x].val < p) return std::max(a[x].val, pre(a[x][], p));
return pre(a[x][], p);
}
int suf(int x, int p)                 //查询在以x为根的子树中,p的后驱是什么
{
if (!x) return 2e9;
if (a[x].val > p) return std::min(a[x].val, suf(a[x][], p));
return suf(a[x][], p);
}
int main()
{
n = read();
for (int i=; i<=n; i++)
{
int tt = read(), x = read();
if (tt==) insert(root, x);
if (tt==) del(root, x);
if (tt==) printf("%d\n",find(root, x));
if (tt==) printf("%d\n",ask(root, x));
if (tt==) printf("%d\n",pre(root, x));
if (tt==) printf("%d\n",suf(root, x));
}
return ;
}

luoguP2286 [HNOI2004]宠物收养场

题目描述

凡凡开了一间宠物收养场。收养场提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物。

每个领养者都希望领养到自己满意的宠物,凡凡根据领养者的要求通过他自己发明的一个特殊的公式,得出该领养者希望领养的宠物的特点值a(a是一个正整数,a<2^31),而他也给每个处在收养场的宠物一个特点值。这样他就能够很方便的处理整个领养宠物的过程了,宠物收养场总是会有两种情况发生:被遗弃的宠物过多或者是想要收养宠物的人太多,而宠物太少。

被遗弃的宠物过多时,假若到来一个领养者,这个领养者希望领养的宠物的特点值为a,那么它将会领养一只目前未被领养的宠物中特点值最接近a的一只宠物。(任何两只宠物的特点值都不可能是相同的,任何两个领养者的希望领养宠物的特点值也不可能是一样的)如果有两只满足要求的宠物,即存在两只宠物他们的特点值分别为a-b和a+b,那么领养者将会领养特点值为a-b的那只宠物。

收养宠物的人过多,假若到来一只被收养的宠物,那么哪个领养者能够领养它呢?能够领养它的领养者,是那个希望被领养宠物的特点值最接近该宠物特点值的领养者,如果该宠物的特点值为a,存在两个领养者他们希望领养宠物的特点值分别为a-b和a+b,那么特点值为a-b的那个领养者将成功领养该宠物。

一个领养者领养了一个特点值为a的宠物,而它本身希望领养的宠物的特点值为b,那么这个领养者的不满意程度为abs(a-b)。

你得到了一年当中,领养者和被收养宠物到来收养所的情况,请你计算所有收养了宠物的领养者的不满意程度的总和。这一年初始时,收养所里面既没有宠物,也没有领养者。

输入输出格式

输入格式:

第一行为一个正整数n,n<=80000,表示一年当中来到收养场的宠物和领养者的总数。接下来的n行,按到来时间的先后顺序描述了一年当中来到收养场的宠物和领养者的情况。每行有两个正整数a, b,其中a=0表示宠物,a=1表示领养者,b表示宠物的特点值或是领养者希望领养宠物的特点值。(同一时间呆在收养所中的,要么全是宠物,要么全是领养者,这些宠物和领养者的个数不会超过10000个)

输出格式:

仅有一个正整数,表示一年当中所有收养了宠物的领养者的不满意程度的总和mod 1000000以后的结果。


题目分析

嗯,好像以前的省选题大多是板子题?

这里用到了求不严格前驱、后驱;插入;删除的基本操作。

然后就是这里的题面的幌子:宠物和领养人的区别。注意到同一时间只可能有宠物或者领养人,并且两种身份的领养规则是一样的。于是我们只需要一颗treap就可以了。

另外注意一下INF的设置,我因为看到题面写着:“(a是一个正整数,a<2^31)”然后就因为INF溢出调了好久……

 #include<bits/stdc++.h>
const long long INF = ;
const long long maxn = ;
const long long MO = ; struct node
{
long long tot,rnd,val,son[];
long long &operator [] (long long a)
{
return son[a];
}
}a[maxn];
long long n,root,cnt;
long long ans;
long long ext[]; inline long long read()
{
char ch = getchar();
long long num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
long long randVal()
{
static long long seed = ;
return seed = (seed*901832ll*seed%)%INF;
}
inline void change(long long x)
{
a[x].tot = a[a[x][]].tot+a[a[x][]].tot+;
}
inline void rotate(long long &x, long long p)
{
long long t = a[x][p];
a[x][p] = a[t][^p],a[t][^p] = x;
change(x), change(t);
x = t;
}
inline void insert(long long &x, long long p)
{
if (!x){
x = ++cnt,a[cnt].tot = ;
a[cnt].val = p,a[cnt].rnd = randVal();
return;
}
a[x].tot++;
if (p <= a[x].val){
insert(a[x][], p);
if (a[a[x][]].rnd < a[x].rnd) rotate(x, );
}else{
insert(a[x][], p);
if (a[a[x][]].rnd < a[x].rnd) rotate(x, );
}
}
void del(long long &x, long long p)
{
if (!x) return;
if (a[x].val==p){
if (a[x][]*a[x][]==){
x = a[x][]+a[x][];
return;
}
if (a[a[x][]].rnd < a[a[x][]].rnd){
rotate(x, );
del(a[x][], p);
}else{
rotate(x, );
del(a[x][], p);
}
}else{
if (a[x].val > p)
del(a[x][], p);
else del(a[x][], p);
}
change(x);
}
long long pre(long long x, long long p)
{
if (!x) return -INF;
if (a[x].val <= p) return std::max(a[x].val, pre(a[x][], p));
return pre(a[x][], p);
}
long long suf(long long x, long long p)
{
if (!x) return INF;
if (a[x].val >= p) return std::min(a[x].val, suf(a[x][], p));
return suf(a[x][], p);
}
void recongnise(long long x)
{
long long a = pre(root, x);
long long b = suf(root, x);
if (b!= INF && abs(b-x)<abs(a-x)){
(ans += 1ll*abs(b-x)) %= MO;
del(root, b);
}else{
(ans += 1ll*abs(a-x)) %= MO;
del(root, a);
}
}
int main()
{
n = read();
for (int i=; i<=n; i++)
{
long long tt = read(), x = read();
if (ext[^tt]){
ext[^tt]--;
recongnise(x);
}else{
ext[tt]++;
insert(root, x);
}
}
if (ans < ) ans = ;
printf("%lld\n",ans);
return ;
}

luoguP2234 [HNOI2002]营业额统计

题目描述

Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。

Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:

当最小波动值越大时,就说明营业情况越不稳定。

而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。

第一天的最小波动值为第一天的营业额。

该天的最小波动值=min{|该天以前某一天的营业额-该天营业额|}。

输入输出格式

输入格式:

输入由文件’turnover.in’读入。

第一行为正整数n(n<=32767) ,表示该公司从成立一直到现在的天数,接下来的n行每行有一个整数ai(|ai|<=1000000) ,表示第i天公司的营业额,可能存在负数。

输出格式:


题目分析

emm……这题只用了pre和suf,权且作为练习写写吧。

 #include<bits/stdc++.h>
const int maxn = ;
const int INF = 2e9; struct node
{
int rnd,val,son[],tot;
int &operator [] (int a)
{
return son[a];
}
}a[maxn];
int n,ans,root,cnt; int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
int randVal()
{
static int seed = ;
return seed = int(seed*305267ll%);
}
inline void change(int x)
{
a[x].tot = a[a[x][]].tot+a[a[x][]].tot+;
}
void rotate(int &x, int p)
{
int t = a[x][p];
a[x][p] = a[t][^p], a[t][^p] = x;
change(x), change(t);
x = t;
}
void insert(int &x, int p)
{
if (x==){
x = ++cnt,a[x].val = p;
a[x].rnd = randVal(), a[x].tot = ;
return;
}
a[x].tot++;
if (p <= a[x].val){
insert(a[x][], p);
if (a[x].rnd > a[a[x][]].rnd) rotate(x, );
}else{
insert(a[x][], p);
if (a[x].rnd > a[a[x][]].rnd) rotate(x, );
}
}
int suf(int x, int p)
{
if (!x) return INF;
if (a[x].val >= p) return std::min(a[x].val, suf(a[x][], p));
return suf(a[x][], p);
}
int pre(int x, int p)
{
if (!x) return -INF;
if (a[x].val <= p) return std::max(a[x].val, pre(a[x][], p));
return pre(a[x][], p);
}
int main()
{
n = read();
for (int i=; i<=n; i++)
{
int x = read();
int a = suf(root, x);
int b = pre(root, x);
if (cnt)
ans += std::min(abs(a-x), abs(b-x));
else ans = x;
insert(root, x);
}
printf("%d\n",ans);
return ;
}

P1486 [NOI2004]郁闷的出纳员

题目描述

OIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。

工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。

老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第k多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。

好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧?

如果某个员工的初始工资低于最低工资标准,那么将不计入最后的答案内

输入输出格式

输入格式:

第一行有两个非负整数n和min。n表示下面有多少条命令,min表示工资下界。

接下来的n行,每行表示一条命令。命令可以是以下四种之一:

名称 格式 作用

I命令 I_k 新建一个工资档案,初始工资为k。如果某员工的初始工资低于工资下界,他将立刻离开公司。

A命令 A_k 把每位员工的工资加上k

S命令 S_k 把每位员工的工资扣除k

F命令 F_k 查询第k多的工资

_(下划线)表示一个空格,I命令、A命令、S命令中的k是一个非负整数,F命令中的k是一个正整数。

在初始时,可以认为公司里一个员工也没有。

输出格式:

输出文件的行数为F命令的条数加一。

对于每条F命令,你的程序要输出一行,仅包含一个整数,为当前工资第k多的员工所拿的工资数,如果k大于目前员工的数目,则输出-1。

输出文件的最后一行包含一个整数,为离开公司的员工的总数。

说明

I命令的条数不超过100000

A命令和S命令的总条数不超过100

F命令的条数不超过100000

每次工资调整的调整量不超过1000

新员工的工资不超过100000


题目大意

需要一个数据结构支持一下的操作:

1.增加元素a

2.把现在所有的元素同时加上或减去数k

3.删除小于值value的所有元素

4.查询排名为k的元素

题目分析

那么就基本等同于treap的操作了。不过这里衍生出了treap的另一种删除操作:删除所有小于value的元素。

 int del(int &x, int p)    //将del改为int:返回值是以x为根的子树中,小于p的元素有几个。
{
if (!x) return ;
if (a[x].val < p)
{
int tt = a[a[x][]].tot+;
x = a[x][];
return tt+del(x, p);
}else{
int tt = del(a[x][], p);
a[x].tot -= tt;
return tt;
}
}

如果理解透彻了treap的del操作,这里的删除应该也是不难理解的。

然后还有一个小技巧:把一起增减的工资用一个delta记录下来,避免了冗余操作。

 #include<bits/stdc++.h>
const int maxn = ; struct node
{
int son[],rnd,val,tot;
int &operator [](int a)
{
return son[a];
}
}a[maxn];
int n,k,cnt,root,sum,delta; int read()
{
char ch = getchar();
int num = ;
for (; !isdigit(ch); ch = getchar());
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
return num;
}
int randVal()
{
static int seed = ;
return seed = (seed*326752ll%);
}
void change(int x)
{
a[x].tot = a[a[x][]].tot+a[a[x][]].tot+;
}
void rotate(int &x, int p)
{
int t = a[x][p];
a[x][p] = a[t][^p], a[t][^p] = x;
change(x), change(t);
x = t;
}
void insert(int &x, int p)
{
if (!x){
x = ++cnt, a[x].tot = ;
a[x].rnd = randVal(), a[x].val = p;
return;
}
a[x].tot++;
if (p <= a[x].val){
insert(a[x][], p);
if (a[x].rnd < a[a[x][]].rnd) rotate(x, );
}else{
insert(a[x][], p);
if (a[x].rnd < a[a[x][]].rnd) rotate(x, );
}
}
int del(int &x, int p)
{
if (!x) return ;
if (a[x].val < p)
{
int tt = a[a[x][]].tot+;
x = a[x][];
return tt+del(x, p);
}else{
int tt = del(a[x][], p);
a[x].tot -= tt;
return tt;
}
}
int ask(int x, int p)
{
if (a[a[x][]].tot == p-) return a[x].val;
if (a[a[x][]].tot >= p) return ask(a[x][], p);
return ask(a[x][], p-a[a[x][]].tot-);
}
int main()
{
n = read(), k = read();
for (int i=; i<=n; i++)
{
char ch = getchar();
for (; !isalpha(ch); ch = getchar());
int x = read();
if (ch=='I'){
if (x >= k)
insert(root, x-delta);
}else if (ch=='A'){
delta += x;
}else if (ch=='S'){
delta -= x;
sum += del(root, k-delta);
}else{
if (x > cnt-sum) printf("-1\n");
else printf("%d\n",ask(root, cnt-sum-x+)+delta);
}
}
printf("%d\n",sum);
return ;
}

P4305 [JLOI2011]不重复数字

题目描述

给出N个数,要求把其中重复的去掉,只保留第一次出现的数。

例如,给出的数为1 2 18 3 3 19 2 3 6 5 4,其中2和3有重复,去除后的结果为1 2 18 3 19 6 5 4。

输入输出格式

输入格式:

输入第一行为正整数T,表示有T组数据。

接下来每组数据包括两行,第一行为正整数N,表示有N个数。第二行为要去重的N个正整数。

输出格式:

对于每组数据,输出一行,为去重后剩下的数字,数字之间用一个空格隔开。

说明

对于30%的数据,1 <= N <= 100,给出的数不大于100,均为非负整数;

对于50%的数据,1 <= N <= 10000,给出的数不大于10000,均为非负整数;

对于100%的数据,1 <= N <= 50000,给出的数在32位有符号整数范围内。 T≤50


题目分析

嗯……这题可以用set直接水过去。不过拿来练习平衡树也是挺好的。

 #include<bits/stdc++.h>
const int maxn = ; struct node
{
int rnd,val,son[];
int &operator [](int a)
{
return son[a];
}
}a[maxn];
int tt,n,cnt,root; int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
int randVal()
{
static int seed = ;
return seed = (seed*6565748ll%);
}
void rotate(int &x, int p)
{
int t = a[x][p];
a[x][p] = a[t][^p], a[t][^p] = x;
x = t;
}
void insert(int &x, int p)
{
if (!x){
x = ++cnt;
a[x].rnd = randVal(),a[x].val = p;
return;
}
if (p <= a[x].val){
insert(a[x][], p);
if (a[x].rnd > a[a[x][]].rnd) rotate(x, );
}else{
insert(a[x][], p);
if (a[x].rnd > a[a[x][]].rnd) rotate(x, );
}
}
bool exist(int x, int p)
{
if (!x) return ;
if (a[x].val == p) return ;
if (p < a[x].val) return exist(a[x][], p);
return exist(a[x][], p);
}
int main()
{
tt = read();
while (tt--)
{
n = read();
memset(a, , sizeof a);
cnt = , root = ;
for (int i=; i<=n; i++)
{
int x = read();
if (!exist(root, x)){
printf("%d ",x);
insert(root, x);
}
}
puts("");
}
return ;
}

END

初涉平衡树「treap」的更多相关文章

  1. 「luogu3380」【模板】二逼平衡树(树套树)

    「luogu3380」[模板]二逼平衡树(树套树) 传送门 我写的树套树--线段树套平衡树. 线段树上的每一个节点都是一棵 \(\text{FHQ Treap}\) ,然后我们就可以根据平衡树的基本操 ...

  2. 「模板」 FHQ_Treap

    「模板」 FHQ_Treap 我也是偶然发现我还没发过FHQ_Treap的板子. 那就发一波吧. 这个速度实在不算快,但是不用旋转,并且好写. 更重要的是,Splay 可以做的事情它都可以做!比如区间 ...

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

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

  4. LOJ 3184: 「CEOI2018」斐波那契表示法

    题目传送门:LOJ #3184. 题意简述: 题目说得很清楚了. 题解: 首先需要了解「斐波那契数系」为何物. 按照题目中定义的斐波那契数列 \(F_n\),可以证明,每个非负整数 \(n\) 都能够 ...

  5. 「SCOI2014」方伯伯的 OJ 解题报告

    「SCOI2014」方伯伯的 OJ 和列队有点像,平衡树点分裂维护即可 但是需要额外用个set之类的对编号查找点的位置 插入完了后记得splay,删除时注意特判好多东西 Code: #include ...

  6. LOJ #2721. 「NOI2018」屠龙勇士(set + exgcd)

    题意 LOJ #2721. 「NOI2018」屠龙勇士 题解 首先假设每条龙都可以打死,每次拿到的剑攻击力为 \(ATK\) . 这个需要支持每次插入一个数,查找比一个 \(\le\) 数最大的数(或 ...

  7. 「模板」 FHQ_Treap 区间翻转

    「模板」 FHQ_Treap 区间翻转 没有旋转的 Treap 实现区间操作的功能,很好理解,也很好写,只是速度不算太快. 对于要翻转的区间,把整棵 Treap(存有区间 \([1,n]\) 的信息) ...

  8. 「LGR-049」洛谷7月月赛 D.Beautiful Pair

    「LGR-049」洛谷7月月赛 D.Beautiful Pair 题目大意 : 给出长度为 \(n\) 的序列,求满足 \(i \leq j\) 且 $a_i \times a_j \leq \max ...

  9. 「NOI2004」「LuoguP1486」郁闷的出纳员

    Descrption OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的工资.这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调 ...

随机推荐

  1. Springboot整合elasticSearch的官方API实例

    前言:在上一篇博客中,我介绍了从零开始安装ElasticSearch,es是可以理解为一个操作数据的中间件,可以把它作为数据的存储仓库来对待,它具备强大的吞吐能力和计算能力,其基于Lucene服务器开 ...

  2. MyBatist庖丁解牛(四)

    什么是MyBatis-Spring? MyBatis-Spring就是帮助你将MyBatis代码无缝的整合到Spring中.Spring将会加载必要的sqlSessionFactory类和sessio ...

  3. Sublime Text 报“Pylinter could not automatically determined the path to lint.py

    Pylinter could not automatically determined the path to lint.py. please provide one in the settings ...

  4. ctypes to load library in c/c++

    cdll.LoadLibrary(...) restype (default is c_int) argtypes (what's the default? c_int?) customized da ...

  5. requests发送HTTPS请求(处理SSL证书验证)

    1.SSL是什么,为什么发送HTTPS请求时需要证书验证? 1.1 SSL:安全套接字层.是为了解决HTTP协议是明文,避免传输的数据被窃取,篡改,劫持等. 1.2 TSL:Transport Lay ...

  6. python学习之图形界面编程:

    一 tkinter:tkinter是python自带的支持tk的库,python代码调用tkinter->tk->操作系统提供的本地GUI(TKL语言开发))完成界面开发,不需要安装任何第 ...

  7. [译]Understanding ECMAScript6 基本知识

    基本知识 ECMAScript 6在ECMAScript 5之上做了大量的改变.一些改变很大,比如添加新的类型或者语法,而其它的非常小,提供了语言之上的渐进改进.这个章节包含了那些渐进改进,它们可能不 ...

  8. JSP文件上传,好烦啊、、

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"% ...

  9. CodeSmith Generator 7.0.2

    [工具]CodeSmith Generator 7.0.2激活步骤 只看楼主 收藏 回复     M炎骫毒逆天T   c#攻城狮 8   学过三层的人应该认识CodeSmith Generator吧, ...

  10. JVM垃圾回收机制二

    对象的回收 垃圾的回收涉及的几个问题:何时回收,由谁回收,怎样回收.这几个问题我们一一来解决. 1.何时回收----对象的生死判定 对象达到什么条件才能判断这个对象已经无用了.常见的判断对象生死的方法 ...