fhq treap
fhq Treap
引入:
平时的 \(treap\) 又长又需要旋转,而且不能求区间序列问题
因此我们用 \(fhqtreap\) 解决。
数组定义:
int ch[MAXN][3];//0 左孩子,1右孩子
int val[MAXN];//每个点的权值
int rnd[MAXN];//每个点的随机权值
int size[MAXN];//以每个点为根的树的大小
更新:
inline void update(int x){
size[x]=1+size[ch[x][0]]+size[ch[x][1]];
}
分裂:
一共有两种分裂:
权值分裂:
当我们遍历到一个节点时,如果它的权值小于 \(k\),那么它的左子树会被分到左边的树里,然后我们遍历它的右儿子,如果大于 \(k\) ,则把它的右子树分到右边的树里。
那么到达递归边界怎么办呢? 有两种情况:
- \(root=0\),要给 \(x=y=0\) 初始化.
- 分裂到了叶子节点,直接返回。
代码:
void split(int now,int k,int &x,int &y){
if(!now) x=y=0;
else if(val[now]<=k){x=now; split(ch[now][1],k,ch[now][1],y);}
else{y=now;split(ch[now][0],k,x,ch[now][0]);}
update(now);//update(i)为更新size[i]大小的函数
}
排名分裂:
排名分裂与权值分裂类似,即将前 \(k\) 个放在一颗树里,其余的放在另一棵树里:
void split(int now,int k,int &x,int &y){
if(!now) x=y=0;
else{
if(k<=siz[ch[now][0]]){ y=now; split(ch[now][0],k,x,ch[now][0]);}
else{x=now;split(ch[now][1],k-siz[ch[now][0]]-1,ch[now][1],y);}
update(now);
}
}
合并:
我们假设第一棵树的权值小于第二棵树的权值,我们就可以比较它们随机权值,如果 \(rnd[l]<rnd[r]\) 那么保留它的左子树,另一棵位右子树,否则相反。
int merge(int A,int B){
if(!A||!B) return A+B;
if(rnd[A]<rnd[B]){
ch[A][1]=merge(ch[A][1],B);
update(A);
return A;
}
else{
ch[B][0]=merge(A,ch[B][0]);
update(B);
return B;
}
}
操作:
插入♂:
直接暴力:
int new_node(int a)//新建一个节点
{
size[++cnt]=1;
val[cnt]=a;
rnd[cnt]=rand();
return cnt;
}
void insert(int a)//插入
{
spilt(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
}
删除:
删除权值为 \(v\) 的点:
- 先把整颗树以 \(v\) 为权值 \(split\) 成两棵树 \(a,b\)。
- 再把 \(a\) 树按照 \(v-1\) 分成 \(c,d\)。这时候值为 \(v\) 的点一定为 \(d\) 的根。
- 那么我们把 \(d\) 的两个子儿子 \(merge\) 起来(这一步就是去除掉 \(v\) 的影响)。
- 再把他们重新 \(merge\) 起来得到一个新的树。
void del(int a){
split(root,a,x,z);
split(x,a-1,x,y);
y=merge(ch[y][0],ch[y][1]);
root=merge(merge(x,y),z);
}
排名:
直接按照 \(a-1\)的权值把树分开,那么 \(x\) 树中最大的应该小于等于 $ a-1$ ,那么 \(a\) 的排名就是 \(size[x]+1\) 。
int myrank(int a){
split(root,a-1,x,y);
int res=size[x]-1;
root=merge(x,y);
return res;
}
第 \(k\) 小值
int findkth(int x){return val[myrank(root,x)];}
前驱后继
我们先看前驱,因为要小于 \(a\),所以我们还是按照 \(a-1\) 的权值划分 \(x\) 。
现在 \(x\) 中最大的数一定小于等于 \(a-1\) ,所以我们直接输出 \(x\) 中最大的数就好。
后继同理。
int pre(int a){//前驱
split(root,a-1,x,y);
int res=val[findKth(x,siz[x])];
root=merge(x,y);
return res;
}
int nxt(int a){//后继
split(root,a,x,y);
int res=val[findKth(y,1)];
root=merge(x,y);
return res;
}
区间问题:
查询一个区间 \([l, r]\) 就把一棵树 \(split\) 成三棵树,查中间那棵,再把它们 \(merge\) 回去。
void add(int l,int r,int delta)//任意操作
{
int x,y,z;
split(root,x,y,r);
split(x,z,x,l-1);
addone(x,delta);//任意操作
merge(x,z,x);
merge(root,x,y);
}
可持久化:
如图,每次 \(split\) 和 \(merge\) 走到的所有点都新建一个即可。注意下传标记也要新建点。
例题:
P3391 【模板】文艺平衡树
# include<iostream>
# include<cstdio>
# include<cstring>
# include<cstdlib>
using namespace std;
const int MAX=1e5+1;
int n,m,tot,rt;
struct Treap{
int pos[MAX],siz[MAX],w[MAX];
int son[MAX][2];
bool fl[MAX];
void pus(int x)
{
siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;
}
int build(int x)
{
w[++tot]=x,siz[tot]=1,pos[tot]=rand();
return tot;
}
void down(int x)
{
swap(son[x][0],son[x][1]);
if(son[x][0]) fl[son[x][0]]^=1;
if(son[x][1]) fl[son[x][1]]^=1;
fl[x]=0;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(pos[x]<pos[y])
{
if(fl[x]) down(x);
son[x][1]=merge(son[x][1],y);
pus(x);
return x;
}
if(fl[y]) down(y);
son[y][0]=merge(x,son[y][0]);
pus(y);
return y;
}
void split(int i,int k,int &x,int &y)
{
if(!i)
{
x=y=0;
return;
}
if(fl[i]) down(i);
if(siz[son[i][0]]<k)
x=i,split(son[i][1],k-siz[son[i][0]]-1,son[i][1],y);
else
y=i,split(son[i][0],k,x,son[i][0]);
pus(i);
}
void coutt(int i)
{
if(!i) return;
if(fl[i]) down(i);
coutt(son[i][0]);
printf("%d ",w[i]);
coutt(son[i][1]);
}
}Tree;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
rt=Tree.merge(rt,Tree.build(i));
for(int i=1;i<=m;i++)
{
int l,r,a,b,c;
scanf("%d%d",&l,&r);
Tree.split(rt,l-1,a,b);
Tree.split(b,r-l+1,b,c);
Tree.fl[b]^=1;
rt=Tree.merge(a,Tree.merge(b,c));
}
Tree.coutt(rt);
return 0;
}
不关注的有难了😠😠😠https://b23.tv/hoXKV9