[NOI2004]郁闷的出纳员(Splay)

寵の児 提交于 2020-02-02 00:29:08

[NOI2004]郁闷的出纳员(Splay)

题目描述
首先这题一看就是道平衡树。我们来考虑如何用平衡树实现这个操作

首先,如果要给员工加工资或者扣工资,平衡树肯定是实现不了。我们要用一个变量来记录目前加了多少工资,我们可以先叫它lazylazy,我们每个询问在取出的时候加上lazylazy就好了。

但是如果一个员工是新加入公司的,那么前面给员工加的工资它肯定是享受不到,为了解决这个问题,我们在每个员工加入时,他的工资要减去lazylazy

这个问题解决了,再结合平衡树的一些知识,这道题就差不多了。我们来总结一下这里的操作怎么实现:

加入员工:直接在平衡树里加入一个节点即可。
加工资:直接 lazy+=klazy += k 即可
扣工资:首先 lazy=klazy -= k 然后我们在平衡树中插入一个节点 lazyminnlazy - minn(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;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!