题目
题解
这道题还是比较好的
平衡树第一题,所以代码打的并不熟练
题目要求使用一种支持点的插入、删除,求名次的数据结构,平衡树当然是首选
题目中的加减操作都是对于所有员工的,我们不可能对所有的点进行修改,于是我们在开一个变量delta,用来记录所有的员工的工资的变化量,那么某个员工的实际工资就是x+delta;
然而我们考虑新加入的员工,对她加上历史的delta显然是不合适的;我们可以这样处理:
在平衡树提前插入inf和-inf
I命令:加入一个员工 我们在平衡树中加入k-minn
A命令:把每位员工的工资加上k delta加k即可
S命令:把每位员工的工资扣除k 此时我们就需要考虑会不会导致一大批员工离开;我们插入minn-delta,然后使小于minn-delta的点一起移动到根的右子树的左子树,一举消灭;
F命令:查询第k多的工资 注意是第k多,Splay操作;
还有一些小细节需要注意,+2-2等等;
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1000001;
const int inf=1e9;
int n,minn,delta,f[maxn],ch[maxn][2],cnt[maxn],size[maxn],key[maxn],rt,sz;
int read()
{
char ch=getchar(); int now=0,f=1;
while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
while (ch>='0' && ch<='9')
{
now=(now<<1)+(now<<3)+ch-'0';
ch=getchar();
}
return now*f;
}
void clear(int x)
{
f[x]=ch[x][0]=ch[x][1]=cnt[x]=key[x]=size[x]=0;
}
bool get(int x) {return ch[f[x]][1]==x;}
void pushup(int x)
{
if (!x) return;
size[x]=cnt[x];
if (ch[x][0]) size[x]+=size[ch[x][0]];
if (ch[x][1]) size[x]+=size[ch[x][1]];
}
void rotate(int x)
{
int old=f[x],oldf=f[old],which=get(x);
ch[old][which]=ch[x][which^1]; f[ch[x][which^1]]=old;
ch[x][which^1]=old; f[old]=x;
f[x]=oldf;
if (oldf) ch[oldf][ch[oldf][1]==old] = x;
pushup(x); pushup(old);
}
void splay(int x,int goal)
{
for (int fa; (fa=f[x])!=goal; rotate(x))//这里是不等于
if (f[fa]!=goal)
rotate(get(x)==get(fa)?fa:x);
if (goal==0) rt=x;
}
void insert(int x)
{
if (rt==0)
{
sz++; key[sz]=x; rt=sz;
cnt[rt]=size[rt]=1;
f[rt]=ch[rt][0]=ch[rt][1]=0;
return;
}
int now=rt,fa=0;
while (1)
{
if (x==key[now])
{
cnt[now]++; pushup(fa); pushup(now); splay(now,0); return;
}
fa=now; now=ch[now][x>key[now]];
if (now==0)
{
sz++; key[sz]=x;
size[sz]=cnt[sz]=1;
f[sz]=fa;
ch[sz][0]=ch[sz][1]=0;
ch[fa][x>key[fa]]=sz;
pushup(fa); splay(sz,0); return;//这里错了
}
}
}
int id(int x)//查询x的编号
{
int now=rt;
while (1)
{
if (x==key[now]) return now;
else
{
if (x<key[now]) now=ch[now][0];
else now=ch[now][1];
}
}
}
int rnk(int x)//查询x的排名
{
int now=rt,ans=0;
while (1)
{
if (x<key[now]) now=ch[now][0];
else
{
ans+=size[ch[now][0]];
if (x==key[now])
{
splay(now,0); return ans+1;//找出x的排名并将其旋转到根
}
ans+=cnt[now];
now=ch[now][1];
}
}
}
int kth(int x)//查询排名为x的数
{
int now=rt;
while (1)
{
if (ch[now][0] && x<=size[ch[now][0]])
now=ch[now][0];
else
{
int tmp=size[ch[now][0]]+cnt[now];
if (x<=tmp) return key[now];
x-=tmp; now=ch[now][1];
}
}
}
int pre()
{
int now=ch[rt][0];
while (ch[now][1]) now=ch[now][1];
return now;
}
int next()
{
int now=ch[rt][1];
while (ch[now][0]) now=ch[now][0];
return now;
}
void del(int x)
{
rnk(x);//将x旋转到根
if (cnt[rt]>1) {cnt[rt]--; pushup(rt); return;}//
if (!ch[rt][0] && !ch[rt][1]) {clear(rt); rt=0; return;}//这里的rt和x写混了,有浪费时间
if (!ch[rt][0]) {
int oldrt=rt; rt=ch[rt][1]; f[rt]=0; clear(oldrt); return;
}
if (!ch[rt][1]) {
int oldrt=rt; rt=ch[rt][0]; f[rt]=0; clear(oldrt); return;
}
int oldrt=rt; int leftbig=pre();
splay(leftbig,0);
ch[rt][1]=ch[oldrt][1];
f[ch[oldrt][1]]=rt;
clear(oldrt);
pushup(rt);
}
int main()
{
n=read(); minn=read();
int totadd=0,totnow=0,ans=0;
char opt[10]; int k;
insert(inf); insert(-inf);
for (int i=1; i<=n; i++)
{
scanf("%s%d",opt,&k);
if (opt[0]=='I')
{
if (k<minn) continue;
insert(k-delta);
totadd++;
}
if (opt[0]=='A') delta+=k;
if (opt[0]=='S')
{
delta-=k;
insert(minn-delta);
int a=id(-inf); int b=id(minn-delta);
splay(a,0);
splay(b,a);
ch[ ch[rt][1] ][0]=0;
del(minn-delta);
}
if (opt[0]=='F')
{
totnow=rnk(inf)-2;
if (totnow<k) {printf("-1\n"); continue;}
int ans=kth(totnow+2-k);
printf("%d\n",ans+delta);//最后再加上累加值delta
}
}
totnow=rnk(inf)-2;
ans=totadd-totnow;
printf("%d",ans);
return 0;
}
总结
积累Splay的操作:查询区间内小于某个数的点一起删掉;
加入+-inf作为根也可以查询平衡树中元素个数的思想;
“线下”维护差值的思想;