首先最好要会写treap(也先了解一下笛卡尔树是什么。。。)
fhq treap和treap同样有一个随机分配的rnd值,用于平衡,但fhq treap不需要旋转操作来维持平衡,因为有两个神奇的操作merge和split
在两种操作之前,要明确的一点是fhq treap依靠rnd值来维护平衡,把每个点按照小根堆的方式放置,即根rnd小于子rnd,并且在任何时候都要保证有二叉搜索树的性质,即左子树key <= 根节点key <= 右子树key
其实我们可以把相等的点缩在一起看做根,这样就满足了左子树<根<右子树了,就算有一些和根相同的点跑到右子树上了,由于我们在各种操作的过程中是不断往右子树上跑的,所以这些点还是会考虑到
下面两个函数是fhq treap的核心,理解好了其他操作就十分简单
注意两个操作都要update(now)
递归的过程,在执行之前需要保证x中所有点的key值(要存储的值,不是随机值)都小于y的(简称为x < y),这是二叉搜索树的性质,而如何保证这个性质先不用着急,在插入操作中,这个性质自然实现了。最后要返回当前根节点
int merge(int x, int y) { //将xy合并为一棵新树 返回当前树根
if(!x || !y) return x + y; // 递归边界
if(tr[x].rnd < tr[y].rnd) {
tr[x].rs = merge(tr[x].rs, y);//把y接在x的右子树上
update(x);
return x;
}
tr[y].ls = merge(x, tr[y].ls);//把x接在y的左子树上
update(y);
return y;
}
权值小于等于k的分到左树(x),大于的分到右树(y)。
注意参数中的x,y是“x树中等待和别的点相接的点,y树种等待和别的点相接的点”这个意思
等待这个词很微妙,下面通过一个具体的情形解释一下
首先x,y均为0,然后判断一下,确定目前x或y的树根
若目前now的值小于等于k,则now应该放在x树上,放在x树的哪里呢?就放在函数目前的参数上,即x“等待相接的位置”,并且x左子树均比k小,不需要改动。
然后现在整棵树变为两部分 now及其左子树,now的右子树,显然在上述情形下,下一次要划分的树就是now的右子树,并且按照二叉搜索树的性质应当把划分下来的,值仍然小于等于k的子树接在x的右子树上,所以此时now的右子树就是“x等待相接的位置”
另一种情况同理
并且不用担心这样分会错(比如在第一种情况中now的右节点还是原来的,但是在不断递归的过程中now的右节点要么被更新,要么由if(!now) x = y = 0;
变为0)
void split(int now, int k, int &x, int &y) {
if(!now) {
x = y = 0;
return; //一定要写return 不要调用到下面的代码,到这里为止了!
}
if(tr[now].val <= k)
x = now, split(tr[now].rs, k, tr[now].rs, y);
else
y = now, split(tr[now].ls, k, x, tr[now].ls);
update(now); //那些now = 0的点不能调用update,因为会使得本来siz为0的点变为1(看update代码,无论如何也会加1)
}
解决区间问题的时候要按size分
这时k为size 若now树的左子树有>=k个节点,那么就从其左子树中找k个
否则,从其右子树中找k-tree[lson].size-1个(左边贡献了足够多的点) 减去的1是根节点(有点像treap求第k大)
void split(int now, int k, int &x, int &y) { // 前k个值组成x,k+1到最后一个值组成y
if(!now) x = y = 0;
else {
if(tr[now].lz) down(now);
if(k <= tr[tr[now].ls].siz) //画画图理解一下,k在now的左子树里就执行下面的语句
y = now, split(tr[now].ls, k, x, tr[now].ls); //now及其右树已分到y上,现在分now的左子树,并且y的等待位置是y的左子节点
else
x = now, split(tr[now].rs, k-tr[tr[now].ls].siz-1, tr[now].rs, y);
update(now);
}
}
void new_node(int val) {
tree[++tot].size = 1, tree[tot].val = val, tree[tot].rnd = rand();
return tot;
}
为了保证"x < y"这个性质,只好按val拆分,然后在合并x和点new的时候虽然x中有和new值相同的,但是问题不大,
split(root, val, x, y);
root = merge(merge(x, new_node(val)), y);
注意合并的时候要反着拆分的顺序操作,仍然是为了保证"x < y"
下面的操作成功分出了一个c,而c的根的值一定为val,这是因为前面划分除出了一个a,a只有两种点,值为val的点和值<=val的点,而c又是从a中分出来的
split(root, val, a, b);
split(a, val-1, a, c);
c = merge(tree[c].lson, tree[c].rson);
root = merge(merge(a,c), b);//a,c最后拆,最先合
把整棵树以 val-1 split 分为x和y 然后答案就是x.size+1,当然前提是这个值存在
和treap一样
int kth(int now, int k) {
while(1) {
int ls = tr[now].ls, rs = tr[now].rs;
if(k <= tr[ls].siz) {
now = ls;
} else {
if(k == tr[ls].siz + 1) {
return now;
} else {
k -= tr[ls].siz + 1;
now = rs;
}
}
}
}
按val-1划分,左树里面找最大的,即在左树里面找rank为左树size的即可
按val划分,右树rank为1的就是
构造一棵treap,使得对treap中序遍历就能得到初始区间
其实应该模仿笛卡尔树,线性建树的,但是我不会,而且大多数题没卡到那个地步
一般就是那种,询问少,n很大,nlogn过不去,但是qlogn能过去的题,就得模仿笛卡尔树建树了,开个单调栈什么的,我目前还不会写。。。
int build(int l, int r) {
if(l > r) return 0;
int mid = l+r>>1;
int pos = new_node(mid);
tr[pos].ls = build(l, mid-1);
tr[pos].rs = build(mid+1, r);
update(pos);
return pos;
}
划分两次,把树分出来一个l~r的,然后进行翻转操作,具体来说就是把一个点的左右儿子交换,对于翻转区间内每一个点都进行这么个操作,最后这个区间就被整体翻转了。这里有懒标记的思想,打上标记把树合并就行了
void reverse(int l, int r) {
int a,b,c,d;
split(root, r, a, b);
split(a, l-1, c, d);
tr[d].lz ^= 1;
root = merge(merge(c, d), b);
}
输出:中序遍历
这里有懒标记的思想,所以输出时还要下传
void dfs(int now) {
if(tr[now].lz) down(now);
if(tr[now].ls) dfs(tr[now].ls);
printf("%d ", tr[now].val);
if(tr[now].rs) dfs(tr[now].rs);
}
有人说要设立两个哨兵节点处理边界。。。但是我没设立也过了,如果你知道为什么的话请在评论区指导一下我。。。
普通平衡树:
#include
#include
#include
#include
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 100000 + 10;
typedef long long ll;
int n,val,opt,a[MAXN],root,x,y,z,tot;
struct fhqtreap{
int ls, rs, val, rnd, siz;
}tr[MAXN];
int newnode(int x) {
tr[++tot].rnd = rand();
tr[tot].val = x;
tr[tot].siz = 1;
return tot;
}
void update(int now) {
tr[now].siz = tr[tr[now].ls].siz + tr[tr[now].rs].siz + 1;
}
int merge(int x, int y) {
if(!x || !y) return x + y;
if(tr[x].rnd < tr[y].rnd) {
tr[x].rs = merge(tr[x].rs, y);
update(x);
return x;
}
tr[y].ls = merge(x, tr[y].ls);
update(y);
return y;
}
void split(int now, int k, int &x, int &y) {
if(!now) {
x = y = 0;
return;
}
if(tr[now].val <= k)
x = now, split(tr[now].rs, k, tr[now].rs, y);
else
y = now, split(tr[now].ls, k, x, tr[now].ls);
update(now);
}
int kth(int now, int k) {
while(1) {
int ls = tr[now].ls, rs = tr[now].rs;
if(k <= tr[ls].siz) {
now = ls;
} else {
if(k == tr[ls].siz + 1) {
return now;
} else {
k -= tr[ls].siz + 1;
now = rs;
}
}
}
}
int main() {
scanf("%d", &n);
while(n--) {
scanf("%d%d", &opt, &val);
if(opt == 1) {
split(root, val, x, y);
root = merge(merge(x, newnode(val)), y);
} else if(opt == 2) {
split(root, val, x, y);
split(x, val-1, x, z);
z = merge(tr[z].ls, tr[z].rs);
root = merge(merge(x, z), y);
} else if(opt == 3) {
split(root, val, x, y);
split(x, val-1, x, z);
printf("%d\n", tr[x].siz+1);
merge(merge(x, z), y);
} else if(opt == 4) {
printf("%d\n", tr[kth(root, val)].val);
} else if(opt == 5) {
split(root, val-1, x, y);
printf("%d\n", tr[kth(x, tr[x].siz)].val);
root = merge(x, y);
} else {
split(root, val, x, y);
printf("%d\n", tr[kth(y, 1)].val);
root = merge(x, y);
}
}
return 0;
}
文艺平衡树:
#include
#include
#include
#include
using namespace std;
const int MAXN = 100000 + 10;
struct trnode{
int val,lz,rnd,ls,rs,siz;
}tr[MAXN];
int n,m,tot,root;
void update(int now) {
tr[now].siz = tr[tr[now].ls].siz + tr[tr[now].rs].siz + 1;
}
void down(int now) {
tr[now].lz ^= 1;
swap(tr[now].ls, tr[now].rs);
tr[tr[now].ls].lz ^= 1;
tr[tr[now].rs].lz ^= 1;
}
int new_node(int val) {
tr[++tot].val = val;
tr[tot].siz = 1;
tr[tot].rnd = rand();
tr[tot].lz = 0; //虽然原来就是0,但是仍然建议写上,当有多次询问时,我们就可以不用memset,而是直接把tot置为0,但是这样就要初始化lz
return tot;
}
int build(int l, int r) {
if(l > r) return 0;
int mid = l+r>>1;
int pos = new_node(mid);
tr[pos].ls = build(l, mid-1);
tr[pos].rs = build(mid+1, r);
update(pos);
return pos;
}
int merge(int x, int y) { //将xy合并
if(!x || !y) return x + y; // 递归边界
if(tr[x].lz) down(x);//下推之前 要先检验是否有,因为用的是异或清理标记,不能随便下推了
if(tr[y].lz) down(y);
if(tr[x].rnd < tr[y].rnd) {
tr[x].rs = merge(tr[x].rs, y);//把y接在x的右子树上
update(x);
return x;
} else {
tr[y].ls = merge(x, tr[y].ls);//把x接在y的左子树上
update(y);
return y;
}
}
void split(int now, int k, int &x, int &y) { // 前k个值组成x,k+1到最后一个值组成y
if(!now) x = y = 0;
else {
if(tr[now].lz) down(now);
if(k <= tr[tr[now].ls].siz) //画画图理解一下,k在now的左子树里就执行下面的语句
y = now, split(tr[now].ls, k, x, tr[now].ls); //now及其右树已分到y上,现在分now的左子树,并且y的等待位置是y的左子节点
else
x = now, split(tr[now].rs, k-tr[tr[now].ls].siz-1, tr[now].rs, y);
update(now);
}
}
void rever(int l, int r) {
int a,b,c,d;
split(root, r, a, b);
split(a, l-1, c, d);
tr[d].lz ^= 1;
root = merge(merge(c, d), b);
}
void dfs(int now) {
if(tr[now].lz) down(now);
if(tr[now].ls) dfs(tr[now].ls);
printf("%d ", tr[now].val);
if(tr[now].rs) dfs(tr[now].rs);
}
int main() {
scanf("%d %d", &n, &m);
root = build(1, n);
for(int i=1; i<=m; i++) {
int l,r;
scanf("%d %d", &l, &r);
rever(l, r);
}
dfs(root);
return 0;
}