1、引入
前置知识:堆(不需要会Treap,或者说根本不用会Treap)
我发现目前用Splay的题目都可以用fhq(不排除我见识短浅的可能)
主要两个特点:
- 整棵树满足二叉搜索树性质:左儿子小于自己,右儿子大于自己
- 整棵树满足堆的性质:每个点附带一个值 k e y key key,值为随机,以key满足堆(可以默认大根堆)
2、操作
新开一个节点
int addnode(int x){
++sz;
val[sz] = x/*值为x*/, key[sz] = rand() * rand()/*key数组随机一个值*/, size[sz] = 1;
return sz;
}
pushup
void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + 1; }
kth
int kth(int now, int k){//跟Splay一样的kth操作
while (1){
if (size[son[now][0]] >= k) now = son[now][0]; else
if (size[son[now][0]] + 1 >= k) return now; else
k -= size[son[now][0]] + 1, now = son[now][1];
}
}
前面三个不重要,真的,相信我
接下来是重头戏,fhq最最最核心的部分
split
- 按照值分裂
void split(int now, int w, int &u, int &v){//按照值w把1棵树掰成2棵 ,左树值均<=w,右树值均>w
//u代表左树的当前节点,v代表右树的当前节点
if (!now) u = v = 0;/*当前为空节点,u、v均为0*/ else{
pushdown(now);//该pushdown的时候不要忘了pushdown
if (val[now] <= w ) u = now, split(son[now][1], w, son[u][1], v);
//当前值<=w,属于左树,那么该点包括左子树都归到左树
//那么将u变为now,u已经有了左儿子,目标找到右儿子 ,所以往下son[u][1]
//now自己和左儿子都有了归属,目标把右儿子弄出去,所以往下son[now][1]
//v什么都没干,往下v
else
v = now, split(son[now][0], w, u, son[v][0]);
//当前值>w,属于右树,那么该点包右子树都归到右树
//那么将v变为now,v已经有了右儿子,目标找到左儿子 ,所以往下son[v][0]
//now自己和右儿子都有了归属,目标把左儿子弄出去,所以往下son[now][0]
//u什么都没干,往下u
pushup(now);//不要忘了pushup
}
}
- 按照size分裂
void split(int now, int w, int &u, int &v){//按照size把1棵树掰成2棵,左树节点数为size
//u代表左树的当前节点,v代表右树的当前节点
if (!now) u = v = 0;/*当前为空节点,u、v均为0*/ else{
pushdown(now);
if (size[son[now][0]] >= w) v = now, split(son[now][0], w, u, son[v][0]);
//如果左儿子节点数>w,说明当前点(包括当前点右儿子)属于右树,
//v为now,目标找到v的左儿子,把now的左儿子弄出去,u什么都没干
else
u = now, split(son[now][1], w - size[son[now][0]] - 1, son[u][1], v);
//否则说明当前点(包括当前左儿子)属于左树,
//u为now,目标找到u的右儿子,把now的右儿子弄出去,v什么都没干
//别忘了把w减去左子树size+1
pushup(now);
}
}
merge
int merge(int u, int v){//合并两棵树,两棵树当前点为u、v
if (!u || !v) return u + v;//如果两点有点为空点,返回另一个非空点
if (key[u] >= key[v]){//满足大根堆性质,key[u]>=key[v]---->v接到u右儿子上
pushdown(u);//不要忘了pushdown
son[u][1] = merge(son[u][1], v);//左树节点在前
pushup(u);//不要忘了pushup
return u;//返回值有讲究,建议理解一下
} else{//否则,把u接到v左儿子上,与上面同理
pushdown(v);
son[v][0] = merge(u, son[v][0]);
pushup(v);
return v;
}
}
3、提升
pushup
理解pushup,就像线段树里的pushup一样,可以同时维护size,sum等等
pushdown
fhq其实也是可以pushdown的,各种tag都可以上
翻转tag,
a
d
d
add
addtag,
m
u
l
t
i
p
l
y
multiply
multiplytag等等
主要就是split里面,merge里面都需要
- 先pushdown
- 操作
- 最后pushup
这样一来,区间问题只要按照size,先把区间分裂出来,再干要干的事(比如打标记等等,就行了)
4、模板题
普通平衡树
Code:
#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
int rt, sz, size[maxn], val[maxn], key[maxn], son[maxn][2];
inline int read(){
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
return s * w;
}
int addnode(int x){
++sz;
val[sz] = x, key[sz] = rand() * rand(), size[sz] = 1;
return sz;
}
void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + 1; }
void split(int now, int w, int &u, int &v){
if (!now) u = v = 0; else{
if (val[now] <= w ) u = now, split(son[now][1], w, son[u][1], v);
else
v = now, split(son[now][0], w, u, son[v][0]);
pushup(now);
}
}
int merge(int u, int v){
if (!u || !v) return u + v;
if (key[u] <= key[v]){
son[v][0] = merge(u, son[v][0]);
pushup(v);
return v;
} else{
son[u][1] = merge(son[u][1], v);
pushup(u);
return u;
}
}
int kth(int now, int k){
while (1){
if (size[son[now][0]] >= k) now = son[now][0]; else
if (size[son[now][0]] + 1 >= k) return now; else
k -= size[son[now][0]] + 1, now = son[now][1];
}
}
int main(){
srand(time(0));
int M = read();
int x, y, z;
while (M--){
int opt = read(), k = read();
if (opt == 1){
split(rt, k, x, y);
rt = merge(merge(x, addnode(k)), y);
} else
if (opt == 2){
split(rt, k, x, y); split(x, k - 1, x, z);
rt = merge(merge(x, merge(son[z][0], son[z][1])), y);
} else
if (opt == 3){
split(rt, k - 1, x, y);
printf("%d\n", size[x] + 1);
rt = merge(x, y);
} else
if (opt == 4) printf("%d\n", val[kth(rt, k)]); else
if (opt == 5){
split(rt, k - 1, x, y);
printf("%d\n", val[kth(x, size[x])]);
rt = merge(x, y);
} else{
split(rt, k, x, y);
printf("%d\n", val[kth(y, 1)]);
rt = merge(x, y);
}
}
return 0;
}
文艺平衡树
Code:
#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
int sz, rt, val[maxn], key[maxn], son[maxn][2], size[maxn], n, m, tag[maxn];
inline int read(){
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
return s * w;
}
int addnode(int x){
++sz;
val[sz] = x, key[sz] = rand() * rand(), size[sz] = 1;
return sz;
}
void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + 1; }
void pushdown(int x){
if (tag[x]){
tag[son[x][0]] ^= 1 ,tag[son[x][1]] ^= 1;
swap(son[x][0], son[x][1]);
tag[x] = 0;
}
}
void split(int now, int w, int &u, int &v){
if (!now) u = v = 0; else{
pushdown(now);
if (size[son[now][0]] >= w) v = now, split(son[now][0], w, u, son[v][0]); else
u = now, split(son[now][1], w - size[son[now][0]] - 1, son[u][1], v);
pushup(now);
}
}
int merge(int u, int v){
if (!u || !v) return u + v;
if (key[u] >= key[v]){
pushdown(u);
son[u][1] = merge(son[u][1], v);
pushup(u);
return u;
} else{
pushdown(v);
son[v][0] = merge(u, son[v][0]);
pushup(v);
return v;
}
}
void write(int now){
if (!now) return;
pushdown(now);
write(son[now][0]);
printf("%d ", val[now]);
write(son[now][1]);
}
int main(){
srand(time(0));
n = read(), m = read();
for (int i = 1; i <= n; ++i) rt = merge(rt, addnode(i));
while (m--){
int l = read(), r = read(), x, y, z;
split(rt, r, x, y);
split(x, l - 1, x, z);
tag[z] ^= 1;
rt = merge(merge(x, z), y);
}
write(rt);
return 0;
}
5、总结
fhq又简单,又实用
爱死了!!
split灵活多变
split好以后不要忘了merge
6、习题
[NOI2004]郁闷的出纳员
送花
宝石管理系统
序列终结者
[HNOI2002]营业额统计
[POI2008]KLO-Building blocks
[APIO2015]八邻旁之桥
[HNOI2004]宠物收养场