平衡树
平衡树
普通平衡树
加强版
vector伪平衡树
数据范围最大是\(10^5\) ,毕竟复杂度是 \(O(n^2 )\)
无O2时千万不要使用
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
vector <int> v;
int n;
int main(){
n=read();
for(int i=1,opt,x;i<=n;i++){
opt=read();x=read();
if(opt==1) v.insert(lower_bound(v.begin(),v.end(),x),x);
if(opt==2) v.erase(lower_bound(v.begin(),v.end(),x));
if(opt==3) printf("%d\n",lower_bound(v.begin(),v.end(),x)-v.begin()+1);
if(opt==4) printf("%d\n",v[x-1]);
if(opt==5) printf("%d\n",*--lower_bound(v.begin(),v.end(),x));
if(opt==6) printf("%d\n",*upper_bound(v.begin(),v.end(),x));
}
return 0;
}
vector套vector
- 把初始序列划分成几段,每段大小长度为800(您也可以试试别的数,比如1000或500)
- 显然如果它一直往一个块里插入就会TLE,于是我们引入 分裂(split)与合并(merge)
- 分裂: 如果一个块大小超过2*S,就分成两块
- 合并: 如果一个块大小小于S/2.就与两边的较小块合并
- 分裂与合并可以使用insert,而不是把一个一个元素拿出来再push_back
- 1,2,3,5,6操作就是二分找到块,然后二分找到元素所在点进行统计(修改)
- 4操作直接从第一个块往后遍历,每过一个块就减去它的大小,直到不能减(找到了)
- 每次操作如果修改了块,就判断是否进行分裂与修改操作
- 复杂度分析: 小常数\(O(n \sqrt n)\),不过跑的非常快(常数极小)
- 注意: 上述 nn 均指数据范围 n+m\
01trie伪平衡树
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int Q=1e7;
struct TREE{
int ch[2],size,end;
};
struct Trie{
TREE t[400005];
int tot;
Trie(){memset(t,0,sizeof(t));tot=1;}
void insert(int x,int val){
int p=1;
for(int i=30;i>=0;i--){
int f=(x>>i)&1;
if(!t[p].ch[f]) t[p].ch[f]=++tot;
p=t[p].ch[f];t[p].size+=val;
}
t[p].end+=val;
}
int get_sum(int x){
int p=1;
for(int i=30;i>=0;i--){
int f=(x>>i)&1;
p=t[p].ch[f];
if(!p) return 0;
}
return t[p].end;
}
int get_rank(int x){
int p=1,res=0;
for(int i=30;i>=0;i--){
int f=(x>>i)&1;
if(f) res+=t[t[p].ch[0]].size;
p=t[p].ch[f];
}
return res+1;
}
int get_kth(int k){
int p=1,res=0;
for(int i=30;i>=0;i--){
int x=t[t[p].ch[0]].size;
if(k<=x) p=t[p].ch[0];
else {
k-=x;
p=t[p].ch[1];
res+=(1<<i);
}
}
return res;
}
int get_pre(int x){
return get_kth(get_rank(x)-1);
}
int get_nxt(int x){
return get_kth(get_rank(x)+get_sum(x));
}
}T;
int n;
int main(){
scanf("%d", &n);
for(int i=1,opt,x;i<=n;i++){
scanf("%d%d",&opt,&x);
if(opt==1) T.insert(x+Q,1);
if(opt==2) T.insert(x+Q,-1);
if(opt==3) printf("%d\n",T.get_rank(x+Q));
if(opt==4) printf("%d\n",T.get_kth(x)-Q);
if(opt==5) printf("%d\n",T.get_pre(x+Q)-Q);
if(opt==6) printf("%d\n",T.get_nxt(x+Q)-Q);
}
return 0;
}
权值线段树
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=4e5+10;
const int INF=0x3f3f3f3f;
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
#define mid ((l+r)>>1)
int rt,tot,ls[N],rs[N],cnt[N];
void add(int l,int r,int k,int v,int &p) {
if(!p) p=++tot;
if(l==r) {
cnt[p]+=v;
return;
}
if(k<=mid) add(l,mid,k,v,ls[p]);
else add(mid+1,r,k,v,rs[p]);
cnt[p]=cnt[ls[p]]+cnt[rs[p]];
}
//求权值区间元素个数
int query(int l,int r,int L,int R,int p) {
if(!p) return 0;
if(L<=l&&r<=R) return cnt[p];
int res=0;
if(L<=mid) res+=query(l,mid,L,R,ls[p]);
if(R>mid) res+=query(mid+1,r,L,R,rs[p]);
return res;
}
int kth(int l,int r,int x,int p) {
if(l==r) return l;
if(cnt[ls[p]]>=x) return kth(l,mid,x,ls[p]);
else return kth(mid+1,r,x-cnt[ls[p]],rs[p]);
}
int get_rk(int l,int r,int k,int p) {
if(!p) return 0;
if(l==r) return 1;
if(k<=mid) return get_rk(l,mid,k,ls[p]);
else return cnt[ls[p]]+get_rk(mid+1,r,k,rs[p]);
}
int get_pre(int x) {
int v=query(-INF,INF,-INF,x-1,rt);
return kth(-INF,INF,v,rt);
}
int get_nxt(int x) {
int v=query(-INF,INF,-INF,x,rt)+1;
return kth(-INF,INF,v,rt);
}
int main() {
int n=read();
int op,x;
while(n--) {
op=read();x=read();
switch(op) {
case 1 : add(-INF,INF,x,1,rt);break;
case 2 : add(-INF,INF,x,-1,rt);break;
case 3 : printf("%d\n",get_rk(-INF,INF,x,1));break;
case 4 : printf("%d\n",kth(-INF,INF,x,1));break;
case 5 : printf("%d\n",get_pre(x));break;
case 6 : printf("%d\n",get_nxt(x));break;
}
}
return 0;
}
权值树状数组(快而简单)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int M=1e7+10;
const int N=1e5+10;
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n;
int c[M],tot;
int op[N],x[N],b[N];
inline int get(int x) {
return lower_bound(b+1,b+1+tot,x)-b;
}
inline void upd(int x,int v) {for(;x<=tot;x+=x&(-x)) c[x]+=v;}
inline int sum(int x) {int res=0;for(;x;x-=x&(-x)) res+=c[x];return res;}
inline int query(int x) {
int t=0;
for(int i=19;i>=0;i--) {
t+=(1<<i);
if(t>tot||c[t]>=x) t-=(1<<i);//注意等号
else x-=c[t];
}
return b[t+1];
}
int main() {
n=read();
for(int i=1;i<=n;i++) {
op[i]=read();x[i]=read();
if(op[i]!=4) b[++tot]=x[i];
}
sort(b+1,b+1+tot);
tot=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++) {
switch(op[i]) {
case 1 : upd(get(x[i]),1);break;
case 2 : upd(get(x[i]),-1);break;
case 3 : printf("%d\n",sum(get(x[i])-1)+1);break;
case 4 : printf("%d\n",query(x[i]));break;
case 5 : printf("%d\n",query(sum(get(x[i])-1)));break;
case 6 : printf("%d\n",query(sum(get(x[i]))+1));break;
}
}
return 0;
}
啊切入正题
平衡树有好多。。。BST, Treap ,Splay ...
ftq——treap
学长强推 fhq 防火墙 treap(也叫无旋treap)
无旋Treap有两种基本操作:
(1)分裂Split
split有两种:一种是按照权值split,一种是按照size来split。
如果按照权值split,那么分出来两棵树的第一棵树上的每一个数的大小都小于(或小于等于)x;
如果按照size split,那么分出来两棵树的第一棵树恰好有x个节点。
我们可以结合具体代码讲解:
(2)合并Merge
将两棵树合为一棵树,其中树A的最大点权小等于树B的最小点权.为了保证树高期望为log,我们要想办法随机合并.
两种合并方法是等价的.都是把其中一个节点当作这一步的根节点,另一个节点和根节点的子树递归合并.请再次回忆二叉搜索树的性质,所以我们一定要保证A在B的"左边".
一定要注意啊,Merge(A,B)和Merge(B,A)天差地别啊!
常规操作
查排名,查前驱后继等操作同普通平衡树,在树上dfs即可.不过为了体现无旋Treap的优越,下方给出的实例代码是利用两种基本操作实现的,优点在于直观,好写,缺点在于比起直接dfs的话常数略大.
插入节点
新建一个节点,然后把树分为x,y两部分,然后把新的节点a看做是一棵树,先与x合并,合并完之后将合并的整体与y合并
删除节点
首先我们把树分为x和z两部分
那么x树中的最大权值为a
再把x分为x和y两部分。
此时x中的最大权值为a-1,且权值为a的节点一定是y的根节点。
然后我们可以无视y的根节点,直接把y的左右孩子合并起来,这样就成功的删除了根节点,
最后再把x,y,z合并起来就好
查询x的排名
我们首先按照a-1的权值把树分开。
那么x树中最大的应该是a-1。
那么a的排名就是siz[x]+1
查询排名为a的数
额直接调用函数,,,函数下面自己看
求x的前驱(前驱定义为小于a,且最大的数)
因为要小于a,那么我们按照a-1的权值划分,
x中最大的一定是<=a-1的,
所以我们直接输出x中最大的数就好,
(这里有一个小技巧,因为siz储存的是节点的数目,然后根据二叉查找树的性质,编号最大的就是值最大的)
区间操作
一般来讲是通过Split剖出你需要操作的区间代表的子树,然后在根节点打标记,然后合并即可.
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
using namespace std;
const int N = 1e5+10;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,tree_cnt,root;
struct fhq_tree{
int ch[2],siz,val,rnd;
}t[N];
inline void pushup(int p) {
t[p].siz=t[t[p].ch[0]].siz+t[t[p].ch[1]].siz+1;
}//注意和线段树不同,加上1(自己)
int insert(int x) {
t[++tree_cnt].siz=1;t[tree_cnt].val=x;t[tree_cnt].rnd=rand();
return tree_cnt;//记得返回
}
inline int merge(int x,int y) {
if(!x||!y) return x|y;
if(t[x].rnd<t[y].rnd) {
t[x].ch[1]=merge(t[x].ch[1],y);
pushup(x);
return x;
} else {
t[y].ch[0]=merge(x,t[y].ch[0]);
pushup(y);
return y;
}
}
inline void split(int p,int k,int &x,int &y) {
if(!p) {x=y=0;return;}
if(t[p].val<=k) {
x=p;
split(t[p].ch[1],k,t[p].ch[1],y);
} else {
y=p;
split(t[p].ch[0],k,x,t[p].ch[0]);
}
pushup(p);
}
int kth(int p,int k) {
while(1) {
if(k<=t[t[p].ch[0]].siz) p=t[p].ch[0];
else {
if(k==t[t[p].ch[0]].siz+1) return p;
else {
k-=t[t[p].ch[0]].siz+1;
p=t[p].ch[1];
}
}
}
}
int main(){
srand(time(0));
n=read();
int x,y,z,u,opt;
for(int i=1;i<=n;i++) {
opt=read();u=read();
if(opt==1) {
split(root,u,x,y);
root=merge(merge(x,insert(u)),y);
} else if(opt==2) {
split(root,u,x,z);//把x完全分出去
split(x,u-1,x,y);
y=merge(t[y].ch[0],t[y].ch[1]);
root=merge(merge(x,y),z);
} else if(opt==3) {
split(root,u-1,x,y);
printf("%d\n",t[x].siz+1);
root=merge(x,y);
} else if(opt==4) {
printf("%d\n",t[kth(root,u)].val);
} else if(opt==5) {
split(root,u-1,x,y);
printf("%d\n",t[kth(x,t[x].siz)].val);
root=merge(x,y);
} else {
split(root,u,x,y);
printf("%d\n",t[kth(y,1)].val);
root=merge(x,y);
}
}
return 0;
}
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
const int N = 2e5+10;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,m,tree_cnt,root,seed=1;
int x,y,z,u,opt;
struct fhq_tree{
int ls,rs,siz,val,rnd;
}t[N];
void pushup(int p) {
t[p].siz=t[t[p].ls].siz+t[t[p].rs].siz+1;
}//注意和线段树不同,加上1(自己)
inline int rand_(void) { return seed *= 482711; }
int merge(int x,int y) {
if(!x||!y) return x|y;
if(t[x].rnd<t[y].rnd) {
t[x].rs=merge(t[x].rs,y);
pushup(x);
return x;
} else {
t[y].ls=merge(x,t[y].ls);
pushup(y);
return y;
}
}
void split(int p,int k,int &x,int &y) {
if(!p) {x=y=0;return;}
if(t[p].val<=k) {
x=p;
split(t[p].rs,k,t[p].rs,y);
} else {
y=p;
split(t[p].ls,k,x,t[p].ls);
}
pushup(p);
}
int insert(int x) {
t[++tree_cnt].siz=1;t[tree_cnt].val=x;t[tree_cnt].rnd=rand_();
split(root,u,x,y);
root=merge(merge(x,tree_cnt),y);
}
int kth(int p,int k) {
while(k!=t[t[p].ls].siz+1)
if(k<=t[t[p].ls].siz) p=t[p].ls;
else k-=t[t[p].ls].siz+1,p=t[p].rs;
return p;
}
int main(){
freopen("3.in","r",stdin);
// srand(time(0));
n=read();m=read();
for(int i=1;i<=n;i++) {
u=read();
insert(u);
}
int last=0,ans=0;
for(int i=1;i<=m;i++) {
opt=read();u=read()^last;
if(opt==1) {
insert(u);
} else if(opt==2) {
split(root,u,x,z);//把x完全分出去
split(x,u-1,x,y);
y=merge(t[y].ls,t[y].rs);
root=merge(merge(x,y),z);
} else if(opt==3) {
split(root,u-1,x,y);
last=t[x].siz+1;
ans^=last;
root=merge(x,y);
} else if(opt==4) {
last=t[kth(root,u)].val;
ans^=last;
} else if(opt==5) {
split(root,u-1,x,y);
last=t[kth(x,t[x].siz)].val;
ans^=last;
root=merge(x,y);
} else {
split(root,u,x,y);
last=t[kth(y,1)].val;
ans^=last;
root=merge(x,y);
}
}
printf("%d\n",ans);
return 0;
}
文艺平衡树——区间翻转
fhq
#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>
using namespace std;
const int N=1000010;
int n,m,tot,root;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
struct fhq_treap{
int siz[N],val[N],rnd[N],ls[N],rs[N];
bool tag[N];
void pushup(int p) {
siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}
int insert(int x) {
siz[++tot]=1;val[tot]=x;rnd[tot]=rand();
return tot;
}
void down(int x) {
swap(ls[x],rs[x]);
if(ls[x]) tag[ls[x]]^=1;
if(rs[x]) tag[rs[x]]^=1;
tag[x]=0;
}
int merge(int x,int y) {
if(!x||!y) return x|y;
if(rnd[x]<rnd[y]) {
if(tag[x]) down(x);
rs[x]=merge(rs[x],y);
pushup(x); return x;
} else {
if(tag[y]) down(y);
ls[y]=merge(x,ls[y]);
pushup(y); return y;
}
}
void split(int p,int k,int &x,int &y) {
if(!p) {x=y=0;return;}
if(tag[p]) down(p);
if(siz[ls[p]]<k) {
x=p;split(rs[p],k-siz[ls[p]]-1,rs[p],y);
} else {
y=p;split(ls[p],k,x,ls[p]);
}
pushup(p);
}
void print(int p) {
if(!p) return;
if(tag[p]) down(p);
print(ls[p]);
printf("%d ",val[p]);
print(rs[p]);
}
}T;
int main() {
srand(time(0));
n=read();m=read();
for(int i=1;i<=n;i++)
root=T.merge(root,T.insert(i));
for(int i=1;i<=m;i++) {
int l,r,a,b,c;
l=read();r=read();
T.split(root,l-1,a,b);
T.split(b,r-l+1,b,c);
T.tag[b]^=1;
root=T.merge(a,T.merge(b,c));
}
T.print(root);
return 0;
}