[NOI 2004]郁闷的出纳员

这是一道练手的水题啦!写网络流怕把splay忘了,于是来练习一下splay。这是一道很经典的splay入门题,用size域查询排名,维护的仍然是有序表,只是由于涉及到删除插入的操作,包括询问排名,所以线段树不是很好实现(但事实上是可以的)。
尽管是道很基础的题,但是还是有很多地方值得注意:
1、由于员工工资可能是一样的,所以如果让重复的元素出现在树中,则会使删除操作变得异常麻烦。于是我们应该添加一个num域表示当前元素在树中出现的个数。那么有两个操作将被修改:
(1). 在进行update更新时,函数应变成

        this->size=l->size+r->size+this->num;

(2). 查找第k大的元素也应该修改,具体看代码

2、在进行删除操作时,千万不能删多了,这个有一些写法上的技巧。

3、splay的关键地方必须及时更新,否则可能会出现奇怪的错误。

4、这个题目的大坑点,如果某员工开始时工资低于最低工资标准,那么他不用算在离开的人数内(出题人语文没学好O__O"…)。

#include <cstdio>
#define __add(x,w) lazy[x]+=w,a[x]+=w
#define update(x) if((x)) s[x]=s[l[x]]+s[r[x]]+num[x]
#define MaxN 100010
using namespace std;
int a[MaxN],l[MaxN],r[MaxN],num[MaxN],s[MaxN],lazy[MaxN];
int n,low,t,tot,ans;
inline void push(const int &x)
{
	if(!x) return;
	__add(l[x],lazy[x]);
	__add(r[x],lazy[x]);
	lazy[x]=0;
}
inline void zig(int &x)
{
	int lc=l[x];
	l[x]=r[lc];
	r[lc]=x;
	update(x);
	x=lc;
}
inline void zag(int &x)
{
	int rc=r[x];
	r[x]=l[rc];
	l[rc]=x;
	update(x);
	x=rc;
}
inline void splay(int &t,int x)
{
	if(!t) return;
	int p,pl=0,pr=0,last;
	for(;;)
	{
		push(t);
		push(l[t]);
		push(r[t]);
		if(x==a[t]) break;
		if(x<a[t]&&!l[t]) break;
		if(x>a[t]&&!r[t]) break;
		if(x<a[t])
		{
			if(x<a[l[t]]&&l[l[t]]) zig(t);
			p=l[t],l[t]=pr,pr=t,t=p;
		}
		else
		{
			if(x>a[r[t]]&&r[r[t]]) zag(t);
			p=r[t],r[t]=pl,pl=t,t=p;
		}
	}
	last=l[t];
	while(pl)
	{
		p=r[pl],r[pl]=last;
		update(pl);
		last=pl,pl=p;
	}
	l[t]=last;
	last=r[t];
	while(pr)
	{
		p=l[pr],l[pr]=last;
		update(pr);
		last=pr,pr=p;
	}
	r[t]=last;
	update(t);
}
inline void insert(const int &x)
{
	if(x<low) return;
	splay(t,x);
	if(!t) a[++tot]=x,t=tot;
	if(x==a[t])
	{
		num[t]++;
		update(t);
	}
	else
	{
		a[++tot]=x;
		num[tot]++;
		if(x<a[t])
			l[tot]=l[t],
			l[t]=tot;
		else
			r[tot]=r[t],
			r[t]=tot;
		update(tot);
		update(t);
	}
}
inline void cheak()
{
	splay(t,low);
	if(a[t]<low) splay(r[t],a[t]),zag(t);
	ans+=s[l[t]];
	l[t]=0;
	update(t);
}
inline int ask(int &x)
{
	if(x>s[t]) return -1;
	int temp=t,cmp;
	for(;;)
	{
		push(temp);
		push(l[temp]);
		push(r[temp]);
		cmp=s[r[temp]]+num[temp];
		if(x<=cmp&&x>s[r[temp]])
			return a[temp];
		else if(x<=s[r[temp]])
			temp=r[temp];
		else
			x-=cmp,
			temp=l[temp];
	}
}
int main()
{
	//freopen("in","r",stdin);
	//freopen("out","w",stdout);
	char s[2];int x;
	scanf("%d%d",&n,&low);
	while(n--)
	{
		scanf("%s%d",s,&x);
		if(s[0]=='I')
			insert(x);
		else if(s[0]=='A')
			__add(t,x);
		else if(s[0]=='S')
			__add(t,-x),
			cheak();
		else printf("%d\n",ask(x));
	}
	printf("%d",ans);
	return 0;
}
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值