題目
題解
這道題還是比較好的
平衡樹第一題,所以代碼打的並不熟練
題目要求使用一種支持點的插入、刪除,求名次的數據結構,平衡樹當然是首選
題目中的加減操作都是對於所有員工的,我們不可能對所有的點進行修改,於是我們在開一個變量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作爲根也可以查詢平衡樹中元素個數的思想;
“線下”維護差值的思想;