题目
OIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,
我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁
闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把
每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资
扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。
工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现
自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且
再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,
我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就
得为他新建一个工资档案。
老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问
现在工资第k多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次
漫长的排序,然后告诉他答案。
好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个
工资统计程序。怎么样,不是很困难吧?
如果某个员工的初始工资低于最低工资标准,那么将不计入最后的答案内
题解
这是本蒟蒻写的第一棵splay呢,啦啦啦
splay,在我的理解里是一种保持二叉查找树(BST)性质的,可以通过旋转操作
将树的高度基本维护在logn,同时可以通过灵活的旋转达到各种操作和目的,平衡树
splay中的左旋和右旋操作和treap是一样的
而splay算法中最核心的部分是splay(x,y)操作,表示把点x旋转到y下面,
当y=0时直接把x旋转到整棵树的根。
splay操作用于维护树的平衡,
每次查找某个点,插入某个点,都要把该点splay到树根,
同时也可用于处理一些特殊操作
splay操作具体步骤如下各图(要旋转的点为儿子点)
1.父亲点为整棵树的根时,直接左旋或右旋一次,下图为右旋示例
2.当儿子点和父亲点同为做儿子或右儿子时,先旋父亲点,然后旋祖父点
下图为右旋示例,左旋则类似
3.当儿子点是左儿子,父亲点是右儿子时,先右旋父亲点,再左旋祖父点
以此类推,当儿子点是右儿子,父亲点是左儿子时,先左旋父亲点,然后右旋祖父点
splay操作要注意细节,左旋操作和右旋操作不要弄反了,可以一边画图一边写。
(555~我就是旋转错了好久,导致tle)
插入insert
先找出适合插入点x的叶子节点位置,然后插入,并将x点splay到树根
查找find
和treap一样,但查找完后要将该点splay到树根
删除del
这题是删除一个1—x的区间,所以把x的父亲splay到树根,直接把左子树删除
当只删除一个点时,首先找到这个数的前驱,把他Splay到根节点
然后找到这个数后继,把他旋转到前驱的底下
比前驱大的数是后继,在右子树
比后继小的且比前驱大的有且仅有当前数
在后继的左子树上面,
因此直接把当前根节点的右儿子的左儿子删掉就可以啦
以下摘自Crash的《运用伸展树解决数列维护问题》
到目前为止,伸展树只是实现了线段树能够实现的功能,下面两个功能将是
线段树无法办到的。
如果我们要在a 后面插入一些数,那么我们先把这些插入的数建成一棵伸展
树,我们可以利用分治法建立一棵完全平衡的二叉树,就是说每次把最中间的作
为当前区间的根,然后左右递归处理,返回的时候进行维护。接着将a 转到根,
将a 后面一个数对应的结点转到根结点的右边,最后将这棵新的子树挂到根右子
结点的左子结点上。
还有一个操作就是删除一个区间a,b内的数,像上面一样,我们先提取区
间,然后直接删除那棵子树,即可达到目的。
最后还需注意的就是,每当进行一个对数列进行修改的操作后,都要维护伸
展树,一种方法就是对影响到的结点从下往上执行Update 操作。但还有一种方
法,就是将修改的结点旋转到根,因为Splay 操作在旋转的同时也会维护每个结
点的值,因此可以达到对整个伸展树维护的目的。
最后还有一个小问题,因为数列中第一个数前面没有数字了,并且最后一个
数后面也没有数字了,这样提取区间时就会出一些问题。为了不进行过多的特殊
判断,我们在原数列最前面和最后面分别加上一个数,在伸展树中就体现为结点,
这样提取区间的时候原来的第k个数就是现在的第k 1个数。并且我们还要注
意,这两个结点维护的信息不能影响到正确的结果。
代码
#include <cstdio>
using namespace std;
struct node{
int l,r,size,data,fa;
}t[100005];
int n,m,add,ans,root,tot;
void updata(int x){
t[x].size=t[t[x].l].size+t[t[x].r].size+1;
}
void rttn(int x){
int y=t[x].l;
if (t[t[x].fa].l==x) t[t[x].fa].l=y;
else t[t[x].fa].r=y;
t[y].fa=t[x].fa;
t[x].fa=y;
t[t[y].r].fa=x;
t[x].l=t[y].r;
t[y].r=x;
updata(x);updata(y);
if (x==root) root=y;
}
void lttn(int x){
int y=t[x].r;
if (t[t[x].fa].l==x) t[t[x].fa].l=y;
else t[t[x].fa].r=y;
t[y].fa=t[x].fa;
t[x].fa=y;
t[x].r=t[y].l;
t[y].l=x;
t[t[x].r].fa=x;
updata(x);updata(y);
if (x==root) root=y;
}
void splay(int x){
while (x!=root){
int f=t[x].fa,p=t[f].fa;
if (f==root)
if (t[f].l==x) rttn(f); else lttn(f);
else
if (t[f].l==x){
if (t[p].l==f) {rttn(f);rttn(p);}
else {rttn(f);lttn(p);}
} else
if (t[p].r==f) {lttn(f);lttn(p);}
else {lttn(f);rttn(p);}
}
}
void insert(int k){
if (!root){
root=++tot;
t[tot].data=k;
t[tot].size=1;
return;
}
int x=root,y=0;
while (x){
y=x;
t[x].size++;
if (t[x].data>k) x=t[x].l;
else x=t[x].r;
}
t[++tot].data=k;
t[tot].size=1;
t[tot].fa=y;
if (t[y].data>k) t[y].l=tot;
else t[y].r=tot;
splay(tot);
}
void del(){
int x=root,y=0,c=0;;
while (x){
if (t[x].data+add<m) x=t[x].r; else
y=x,x=t[x].l;
}
if (!y){
ans+=t[root].size;
root=0;
return;
}
splay(y);
if (t[y].l) ans+=t[t[y].l].size;
t[y].l=0;
t[y].size=t[t[y].r].size+1;
}
int find(int k){
int x=root;
while (t[t[x].l].size+1!=k){
if (t[t[x].l].size+1<k)
k-=t[t[x].l].size+1,x=t[x].r;
else x=t[x].l;
}
splay(x);
return t[x].data;
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++){
char ch[2];
int x;
scanf("%s%d",ch,&x);
if (ch[0]=='I')
if (x>=m) insert(x-add);
if (ch[0]=='A') add+=x;
if (ch[0]=='S') add-=x,del();
if (ch[0]=='F')
if (x>tot-ans) printf("-1\n");
else printf("%d\n",find(tot-ans-x+1)+add);
}
printf("%d\n",ans);
}