【平衡树做题记录】
用的 fhq_treap 不得不说比 splay 好写多了
(虽然以后写LCT还是要用是play)
P1110 报表统计
考虑维护两个平衡树 一个维护相邻两数的差 一个维护不相邻的数差
实际上每次插入一个数,维护相邻数的平衡树需要删掉一个原本相邻的差值并加入两个新的值,而不相邻的极差只需要每次操作时都更新一下答案即可。
同时维护一下每一个数往后面插入的最后一个数是谁 这题就可以切了。
但代码不知道为什么被卡空间了
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
inline int read(){
int s=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){
s=s*10-48+ch;
ch=getchar();
}
return s;
}
int tr[MAXN][2],val[MAXN],cv[MAXN],siz[MAXN],cnt;
int n,m,a[500001],pre[500001],rt,A2=(1<<30),RT;
inline int Min(int x,int y){return x<y?x:y;}
char O[20];
int t[MAXN][2],v[MAXN],C[MAXN],tot;
const int inf=(1<<30);
int A3=(1<<30),b[500001];
inline void pushup(int x){siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+1;}
inline int rd(){return rand();}
inline int BD(int x){
v[++tot]=x;
C[tot]=rd();
return tot;
}
inline int bd(int x){
val[++cnt]=x;
siz[cnt]=1;
cv[cnt]=rd();
return cnt;
}
void split(int now,int k,int &x,int &y){
if(!now){x=y=0;return;}
if(val[now]<=k)x=now,split(tr[now][1],k,tr[now][1],y);
else y=now,split(tr[now][0],k,x,tr[now][0]);
pushup(now);
}
void Split(int now,int k,int &x,int &y){
if(!now){x=y=0;return;}
if(v[now]<=k)x=now,Split(t[now][1],k,t[now][1],y);
else y=now,Split(t[now][0],k,x,t[now][0]);
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(cv[x]<cv[y]){
tr[x][1]=merge(tr[x][1],y);
pushup(x);return x;
}
else{
tr[y][0]=merge(x,tr[y][0]);
pushup(y);return y;
}
}
int Merge(int x,int y){
if(!x||!y)return x+y;
if(C[x]<C[y]){
t[x][1]=Merge(t[x][1],y);
return x;
}
else{
t[y][0]=Merge(x,t[y][0]);
return y;
}
}
inline int Abs(int x){
if(x<0)x=-x;
return x;
}
int kth(int now,int k){
if(k<=siz[tr[now][0]])return kth(tr[now][0],k);
else if(k==siz[tr[now][0]]+1)return now;
else return kth(tr[now][1],k-siz[tr[now][0]]-1);
}
void del(int vv){
int x,y,z;
Split(RT,vv,x,z);
Split(x,vv-1,x,y);
y=Merge(t[y][0],t[y][1]);
RT=Merge(Merge(x,y),z);
}
int P(int vv){
int x,y;
split(rt,vv,x,y);
int G=val[kth(x,siz[x])];
rt=merge(x,y);
return G;
}
int L(int vv){
int x,y;
split(rt,vv-1,x,y);
int G=val[kth(y,1)];
rt=merge(x,y);
return G;
}
void Ins(int vv){
int x,y;
split(rt,vv,x,y);
rt=merge(merge(x,bd(vv)),y);
}
void Insert(int vv){
int x,y;
Split(RT,vv,x,y);
RT=Merge(Merge(x,BD(vv)),y);
}
int main(){
srand(time(0));
Ins(-inf);Ins(inf);
n=read();m=read();
for(int i=1;i<=n;++i){
a[i]=read();
b[i]=a[i];
pre[i]=a[i];
Ins(a[i]);
}
sort(b+1,b+n+1);
for(int i=1;i<n;++i){A3=Min(A3,b[i+1]-b[i]);Insert(Abs(a[i+1]-a[i]));Ins(a[i]);}
Ins(a[n]);
for(;m;m--){
scanf("%s",O);
if(O[0]=='I'){
int A,B;
A=read(),B=read();
del(Abs(pre[A]-a[A+1]));
Insert(Abs(B-pre[A]));
Insert(Abs(B-a[A+1]));
pre[A]=B;
int T=RT;
while(t[T][0])T=t[T][0];
A2=v[T];
int PB=P(B),LB=L(B);
Ins(B);
int R=Min(B-PB,LB-B);
A3=Min(A3,R);
}
else if(O[4]=='G'){printf("%d\n",A2);}
else{printf("%d\n",A3);}
}
return 0;
}
P1486 郁闷的出纳员
由于是全局修改 考虑如何消除后面插入数于之前全局修改的值的影响。
我们可以将 \(v-tag\) 插入。这样就可以避免掉之前 \(tag\) 对我们现在的值的影响。
每次扣工资我们都要检查一下是不是有人要走,将树以 \(Minn-tag\) 分裂,将小的那部分直接砍掉即可。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
int n,Mn;
int tr[MAXN][2],siz[MAXN],cv[MAXN],val[MAXN],cnt;
int Ttag,rt;
char opt[2];
inline int rd(){return rand()<<15|rand();}
inline int bd(int c){
siz[++cnt]=1;
val[cnt]=c;
cv[cnt]=rd();
return cnt;
}
inline void pushup(int x){siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+1;}
void split(int now,int k,int &x,int &y){
if(!now){x=y=0;return;}
if(val[now]<=k)x=now,split(tr[now][1],k,tr[now][1],y);
else y=now,split(tr[now][0],k,x,tr[now][0]);
pushup(now);
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(cv[x]<cv[y]){
tr[x][1]=merge(tr[x][1],y);
pushup(x);return x;
}
else{
tr[y][0]=merge(x,tr[y][0]);
pushup(y);return y;
}
}
int kth(int now,int k){
if(k<=siz[tr[now][0]])return kth(tr[now][0],k);
else if(k==siz[tr[now][0]]+1)return now;
else return kth(tr[now][1],k-siz[tr[now][0]]-1);
}
int All;
int main(){
scanf("%d%d",&n,&Mn);
for(;n;n--){
int v;
scanf("%s",opt);
scanf("%d",&v);
if(opt[0]=='I'){
if(v<Mn)continue;
int x,y;
split(rt,(v-Ttag),x,y);
rt=merge(merge(x,bd(v-Ttag)),y);
All++;
}
else if(opt[0]=='A'){Ttag+=v;}
else if(opt[0]=='S'){
Ttag-=v;
int x,y;
split(rt,(Mn-Ttag-1),x,y);
rt=y;
}
else{
if(v>siz[rt])puts("-1");
else printf("%d\n",val[kth(rt,siz[rt]-v+1)]+Ttag);
}
}
printf("%d\n",All-siz[rt]);
return 0;
}
P3850 书架
考虑 fhq_treap 按照siz分裂。这样就可以做到指定区间插入指定数了。
以siz为标 剩下的就是查询第k大了
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
inline int rd(){
return rand()<<15|rand();
}
int tr[MAXN][2],siz[MAXN],cv[MAXN],cnt;
int val[MAXN];
int n,m,rt,q;
string opt,Nm[MAXN],id[MAXN];
inline void pushup(int x){
siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+1;
}
inline int bd(int v,string P){
val[++cnt]=v;
siz[cnt]=1;
cv[cnt]=rd();
Nm[cnt]=P;
return cnt;
}
void split(int now,int k,int &x,int &y){
if(!now){x=y=0;return;}
if(k<=siz[tr[now][0]])y=now,split(tr[now][0],k,x,tr[now][0]);
else x=now,split(tr[now][1],k-siz[tr[now][0]]-1,tr[now][1],y);
pushup(now);
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(cv[x]<cv[y]){
tr[x][1]=merge(tr[x][1],y);
pushup(x);return x;
}
else{
tr[y][0]=merge(x,tr[y][0]);
pushup(y);return y;
}
}
int kth(int now,int k){
if(k<=siz[tr[now][0]])return kth(tr[now][0],k);
else if(k==siz[tr[now][0]]+1)return now;
else return kth(tr[now][1],k-siz[tr[now][0]]-1);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
int x,y;
cin>>id[i];
split(rt,i-1,x,y);
rt=merge(merge(x,bd(i,id[i])),y);
}
scanf("%d",&m);
for(int i=1;i<=m;++i){
cin>>opt;
int v,x,y;
scanf("%d",&v);
v++;
split(rt,v-1,x,y);
rt=merge(merge(x,bd(v,opt)),y);
}
scanf("%d",&q);
for(int i=1;i<=q;++i){
int v;
scanf("%d",&v);
v++;
int pos=kth(rt,v);
cout<<Nm[pos]<<endl;
}
return 0;
}
P2596 书架
考虑一般的序列上这种拿出来再插入所需要的信息:这个编号的书左边有多少本书(要不然无法分裂出这本书)
原本是想直接维护这个但思来想去不知道怎么维护……看了题解后学到了:
维护书的编号所对应的节点编号,通过维护树上编号的父亲,当路径上的点是右子树的时候,将它左子树的 siz 加上这个点 记录下来,它们都处于这本书的左边。
这样就可以 \(O(\log n)\) 处理出一个点左边的点的个数。到这里之后剩下的就真的是无脑套分裂合并了。
注意分裂的时候要维护好原来书的顺序。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
inline int rd(){return rand()<<15|rand();}
int pos[MAXN];
int tr[MAXN][2],cv[MAXN],siz[MAXN],cnt,n,m,rt,val[MAXN],pa[MAXN];
inline void pushup(int x){siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+1;pa[tr[x][0]]=pa[tr[x][1]]=x;}
inline int build(int v){
siz[++cnt]=1;
val[cnt]=v;
cv[cnt]=rd();
return cnt;
}
void split(int now,int k,int &x,int &y){
if(!now){x=y=0;return;}
if(k<=siz[tr[now][0]])y=now,split(tr[now][0],k,x,tr[now][0]);
else x=now,split(tr[now][1],k-siz[tr[now][0]]-1,tr[now][1],y);
pushup(now);
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(cv[x]<cv[y]){
tr[x][1]=merge(tr[x][1],y);
pushup(x);return x;
}
else {
tr[y][0]=merge(x,tr[y][0]);
pushup(y);return y;
}
}
int kth(int now,int k){
if(k<=siz[tr[now][0]])return kth(tr[now][0],k);
else if(k==siz[tr[now][0]]+1)return now;
else return kth(tr[now][1],k-siz[tr[now][0]]-1);
}
int getpos(int v){
int res=1+siz[tr[v][0]];
while(pa[v]){
if(v==tr[pa[v]][1])res+=1+siz[tr[pa[v]][0]];
v=pa[v];
}
return res;
}
char opt[10];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
int x,y,nd;
scanf("%d",&nd);
split(rt,i-1,x,y);
rt=merge(merge(x,build(nd)),y);
pos[nd]=cnt;
}
for(;m;m--){
int x,y,z,w;
int s,t;
scanf("%s",opt);
if(opt[0]=='T'){
scanf("%d",&s);
int u=getpos(pos[s]);
split(rt,u-1,x,z);
split(z,1,y,w);
rt=merge(y,merge(x,w));
}
else if(opt[0]=='B'){
scanf("%d",&s);
int u=getpos(pos[s]);
split(rt,u-1,x,z);
split(z,1,y,w);
rt=merge(merge(x,w),y);
}
else if(opt[0]=='I'){
scanf("%d%d",&s,&t);
if(t==0)continue;
int u=getpos(pos[s]);
split(rt,u-1,x,z);
split(z,1,y,w);
rt=merge(x,w);
split(rt,u-1+t,x,z);
rt=merge(merge(x,y),z);
}
else if(opt[0]=='A'){
scanf("%d",&s);
int u=getpos(pos[s]);
printf("%d\n",u-1);
}
else{
scanf("%d",&s);
printf("%d\n",val[kth(rt,s)]);
}
}
return 0;
}