[NOI2004]郁闷的出纳员(Splay)
题目描述
首先这题一看就是道平衡树。我们来考虑如何用平衡树实现这个操作
首先,如果要给员工加工资或者扣工资,平衡树肯定是实现不了。我们要用一个变量来记录目前加了多少工资,我们可以先叫它,我们每个询问在取出的时候加上就好了。
但是如果一个员工是新加入公司的,那么前面给员工加的工资它肯定是享受不到,为了解决这个问题,我们在每个员工加入时,他的工资要减去
这个问题解决了,再结合平衡树的一些知识,这道题就差不多了。我们来总结一下这里的操作怎么实现:
加入员工:直接在平衡树里加入一个节点即可。
加工资:直接 即可
扣工资:首先 然后我们在平衡树中插入一个节点 (minn是工资下界,也就意味着比这个工资低的都要删除)
我们将这个点旋转到根,并直接将根的左子树全部歼灭(将根的左儿子设为0)
第k大:基本平衡树操作
贴个代码`
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
#define ll long long
#define FOR(i,a,b) for(int i = a;i <= b;i++)
#define _FOR(i,a,b) for(int i = a;i >= b;i--)
template<typename T> void read(T &x)
{
x = 0;int f = 1;
char c = getchar();
for(;!isdigit(c);c = getchar()) if(c == '-') f = -1;
for(;isdigit(c);c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n,minn;
int root,tot;//平衡树的根,以及目前有几个节点
int fa[N],son[N][2],val[N],cnt[N],size[N];//节点的爸爸,节点的两个儿子,节点的值,节点数字的数量 ,子树的大小
int F(int x) {return son[fa[x]][1] == x;} //判断一个节点是左儿子还是右儿子
void pushup(int x)//及时更新子树大小
{
if(x)
{
size[x] = cnt[x];
if(son[x][0]) size[x] += size[son[x][0]];
if(son[x][1]) size[x] += size[son[x][1]];
}
}
void rotate(int x)
{
int y = fa[x];
int z = fa[y];//x的爸爸和x的爷爷
int tmp = F(x);//x是左儿子还是右儿子
son[y][tmp] = son[x][tmp ^ 1];//y的x这边的儿子变为x在另一边的儿子
fa[son[y][tmp]] = y;//他的爸爸也要变成y
son[x][tmp ^ 1] = y;//x的另一边的儿子变为y
fa[y] = x;
fa[x] = z;//x,y的爸爸也要改变
if(z) son[z][son[z][1] == y] = x;
pushup(y),pushup(x);//改变x,y的子树大小
}
void Splay(int x,int QAQ)//将x旋转到y的儿子
{
while(fa[x] != QAQ)
{
int y = fa[x];//x的父亲节点]
int z = fa[y];//x的祖父节点
if(z != QAQ)//如果z不是goal
rotate(F(x) == F(y) ? y : x);
//如果x和y同为左儿子或者右儿子先旋转y
//如果x和y不同为左儿子或者右儿子先旋转x
//如果不双旋的话,旋转完成之后树的结构不会变化
rotate(x);//再次旋转x,将x旋转到z的位置
}
if(!QAQ) root = x;
}
void ins(int x)
{
if(!root)//树空
{
++tot;
fa[tot] = son[tot][0] = son[tot][1] = 0;
size[tot] = cnt[tot] = 1;val[tot] = x;
root = tot;
return ;
}
int u = root,fath = 0;//从根节点往下走
while(1)
{
if(val[u] == x)//原本有节点
{
cnt[u]++;
pushup(u),pushup(fath);
Splay(u,0);
return ;
}
fath = u,u = son[u][x > val[u]];
if(!u)//新建节点
{
++tot;
son[tot][0] = son[tot][1] = 0;
size[tot] = cnt[tot] = 1;
son[fath][x > val[fath]] = tot;
fa[tot] = fath;
val[tot] = x;
Splay(tot,0);
return ;
}
}
}
int find(int x)//寻找x,如果f = 1表示要将他旋转到根
{
int u = root;
if(!u) return 0;
while(son[u][x > val[u]] && val[u] != x) u = son[u][x > val[u]];
Splay(u,0);
return u;
}
int pre_next(int x,int f)//0表示找前驱,1表示找后继
{
find(x);
int u = root;//根节点,此时x的父节点(存在的话)就是根节点
if(val[u] > x && f)return u;//如果当前节点的值大于x并且要查找的是后继
if(val[u] < x && !f)return u;//如果当前节点的值小于x并且要查找的是前驱
u = son[u][f];//查找后继的话在右儿子上找,前驱在左儿子上找
while(son[u][f ^ 1]) u = son[u][f ^ 1];//要反着跳转,否则会越来越大(越来越小)
return u;//返回位置
}
void Delete(int x)
{
int pre = pre_next(x,0);
int next = pre_next(x,1);
Splay(pre,0),Splay(next,pre);
int u = son[next][0];
if(cnt[u] > 1)
cnt[u]--,Splay(u,0);
else son[next][0] = 0;
}
int kth(int x)
{
int u = root;
while(1)
{
if(son[u][0] && x <= size[son[u][0]]) u = son[u][0];
else
{
int sum = size[son[u][0]] + cnt[u];
if(x <= sum) return val[u];
u = son[u][1];
x -= sum;
}
}
}
int rank(int x)
{
int u = root,res = 0;
while(1)
{
if(x < val[u]) u = son[u][0];
else
{
res += size[son[u][0]];
if(x == val[u])
{
Splay(u,0);
return res + 1;
}
res += cnt[u];
u = son[u][1];
}
}
}
int main()
{
//freopen("2.in","r",stdin);
//freopen(".out","w",stdout);
ins(1e9),ins(-1e9);
read(n),read(minn);
int ans = 0,lazy = 0;//答案,懒标记
FOR(i,1,n)
{
char op[10];int x;
scanf("%s",op);read(x);
if(op[0] == 'I')
if(x >= minn) ins(x - lazy),ans++;//要减懒标记因为取出来的时候还会加上
if(op[0] == 'A') lazy += x;
if(op[0] == 'S')
{
lazy -= x;
ins(minn - lazy);
int tmp1 = find(-1e9);
int tmp2 = find(minn - lazy);
Splay(tmp1,0);Splay(tmp2,tmp1);
son[son[root][1]][0] = 0;
Delete(minn - lazy);
}
if(op[0] == 'F')
{
int now = rank(1e9) - 2;
//printf("QAQ%d\n",now);
if(now < x) puts("-1");
else printf("%d\n",kth(now + 2 - x) + lazy);
}
}
ans -= rank(1e9) - 2;
printf("%d\n",ans);
return 0;
}
来源:CSDN
作者:郭佳明
链接:https://blog.csdn.net/gjm2005/article/details/104135031