【BZOJ4825】[HNOI2017] 单旋(树状数组)
大致题意: 定义"单旋Splay"为\(Spaly\),对一棵\(Spaly\)进行插入、\(Spaly\)最值、\(Spaly\)并删除最值操作,求每次操作的复杂度(操作节点深度)。
找规律
这题乍一看似乎根本不可做,但要注意,题目中需要\(Spaly\)的对象仅仅是最值!
显然,最小值就是不断\(Zig\),最大值就是不断\(Zag\)。
然后找一找规律,就会发现,以最小值为例,实际上就是它的右儿子占据了它原先的位置(变成了它父节点的左儿子),然后原先的根节点变成了它的新右儿子。
也就是说,它自身深度变成\(1\),子树内的点深度不变,其余点深度加\(1\)。
如果还要删除最值,就是子树内的点深度减\(1\),其余点深度不变(当然也可以先按不删除的做,然后给所有点深度减\(1\),二者本质相同)。
由于\(Spaly\)和\(Splay\)一样,是一棵平衡树,因此中序遍历是有序的,因此我们只要记录每个点的父节点,就可以方便地求出子树对应值域了。
插入新值
在平衡树中插入一个新值实际上是有规律的。
我们可以把平衡树从父节点转移到子节点这一步看作一个类似于二分的过程,则显然,插入一个数必然会经过它的前驱和后继对应的点,且最后到达的点一定是前驱和后继中深度较大的点(这个可以自己画图理解),那么它的深度就是这个较大深度加\(1\)。
要维护前驱和后继,直接用\(set\)就好了(当然如果你闲着没事也可以去写个\(Splay\)。。。)。
具体实现可能需要注意点细节。
树状数组
整理一下我们要干些什么:单点赋值、区间加法、单点求值。
显然一个树状数组就可以了,其中单点赋值可以转化为先单点求值,然后在原值的基础上修改一个长度为\(1\)的区间。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,a[N+5],op[N+5],qv[N+5];struct node {int F,S[2];}O[N+5];set<int> P;set<int>::iterator it;
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
#undef D
}F;
class TreeArray
{
private:
int a[N+5];I void D(RI x,CI v) {W(x<=n) a[x]+=v,x+=x&-x;}
public:
I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//单点求值
I void U(CI l,CI r,CI v) {D(l,v),D(r+1,-v);}//区间修改
I void T(CI x,CI v) {RI t=v-Q(x);D(x,t),D(x+1,-t);}//单点赋值
}S;
int main()
{
RI Qt,i;for(F.read(Qt),i=1;i<=Qt;++i) F.read(op[i]),op[i]==1&&(F.read(qv[i]),a[++n]=qv[i]);//存储询问,因为要离散化
#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
RI rt,x,u,v,du,dv;for(sort(a+1,a+n+1),i=1;i<=Qt;++i) switch(op[i])
{
#define Ans(d) S.T(x,d),F.writeln(d)
case 1:x=lower_bound(a+1,a+n+1,qv[i])-a;if(P.empty()) {rt=x,Ans(1);goto End;}//原树是空树
du=(it=P.lower_bound(x))==P.begin()?0:S.Q(u=*--it),//前驱(注意考虑不存在的情况)
dv=(it=P.upper_bound(x))==P.end()?0:S.Q(v=*it),//后记
du>dv?(Ans(du+1),Co(x,u,1)):(Ans(dv+1),Co(x,v,0));End:P.insert(x);break;//当前深度为较大深度加1
case 2:F.writeln(S.Q(x=*P.begin())),O[x].F&&//如果当前点是根则不处理
(S.T(x,1),S.U(O[x].F,n,1),Co(O[x].S[1],O[x].F,0),Co(rt,x,1),O[rt=x].F=0);break;//修改深度以及父子关系
case 3:F.writeln(S.Q(x=*--P.end())),O[x].F&&//同上
(S.T(x,1),S.U(1,O[x].F,1),Co(O[x].S[0],O[x].F,1),Co(rt,x,0),O[rt=x].F=0);break;
case 4:F.writeln(S.Q(x=*P.begin())),P.erase(x),//从set中删除当前点
S.U(x+1,O[x].F?O[x].F-1:n,-1),O[(O[x].F?O[O[x].F].S[0]:rt)=O[x].S[1]].F=O[x].F;break;//注意分是否为根节点讨论
case 5:F.writeln(S.Q(x=*--P.end())),P.erase(x),//同上
S.U(O[x].F?O[x].F+1:1,x-1,-1),O[(O[x].F?O[O[x].F].S[1]:rt)=O[x].S[0]].F=O[x].F;break;
}return F.clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒