前言
这篇博客我自认为写的非常清楚,不需要$\text任何基础$,只要会C++语言基础即可学懂。
任何一个地方我都没有默认已经学过了,完全从0开始的$\text FHQ-Treap$教学!
好耶!我们开始吧!ヽ(✿゚▽゚)ノ
哒哒哒哒哒!
FHQ-Treap
FHQ-Treap是一种平衡树。名字里带有Treap,他的确与Treap有一些相同的性质。比如,他与Treap一样,通过随机权值来保证平衡。
可是,众所周知,Treap是一种通过旋转来维护平衡的平衡树。
范浩强(FHQ)说过这样一句话:
$\text{Think functional.}$
$\text{—–FHQ}$
这句话是什么意思呢?
如果不明白,可以看这篇文章以获得一些启发。
既然要$\text{Think functional}$,自然就不能使用旋转操作了。
于是,通过拆开与合并来维护平衡的FHQ-Treap就诞生了。
主要操作
结构体
1 2 3 4 5 6 7
| struct Node { long long lch, rch; long long siz; long long val; long long rnd; }
|
生成随机权值
想不到吧,这个都会讲!就是这么细!
首先为了让权值随机,我们需要初始化种子。
但是这里要注意的是,初始化种子只需要初始化一次,生成的数就是随机的了,多次初始化反而会导致总是生成同一个数。
Linux下生成的随机数比较大,我们不需要那么大的,可以取个模。
拆分
拆分操作,就是把一棵树拆成两棵,以在两棵树中间进行一些操作。
设定一个$K$,然后把比$K$小或者等于$K$的放在左侧,其余在右侧。
如图:
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void split(long long nowX, long long K, long long &Xtree, long long &Ytree)
{ if (!nowX) { Xtree = Ytree = 0; } else { if (nodes[nowX].val <= K) { Xtree = nowX; split(nodes[nowX].rch, K, nodes[nowX].rch, Ytree); } else { Ytree = nowX; split(nodes[nowX].lch, K, Xtree, nodes[nowX].lch); } update(nowX); } }
|
合并
合并,就是拆分完操作完后再将树合并回去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| long long merge(long long Xtree,long long Ytree) { if(!Xtree||!Ytree) { return Xtree+Ytree; } else if(nodes[Xtree].rnd<=nodes[Ytree].rnd) { nodes[Xtree].rch=merge(nodes[Xtree].rch,Ytree); update(Xtree); retrun Xtree; } else { nodes[Ytree].lch=merge(Ytree,nodes[Xtree].lch); update(Ytree); retrun Ytree; } }
|
删除
很显然,将要删除的数分裂出来之后不合并回去即可。
1 2 3 4 5 6 7 8
| long long del(long long nowX) { split(rot,nowX,X,Z); split(X,nowX-1,X,Y); Y=merge(nodes[nowX].lch,nodes[nowX].rch); rot=merge(merge(X,Y),Z);
}
|
排名第K的数
常规做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| long long Kth(long long nowX, long long K) { while (1) { if (K == nodes[nodes[nowX].lch].siz + 1) { return nowX; } else if (K > nodes[nodes[nowX].lch].siz + 1) { nowX = nodes[nowX].rch; K -= nodes[nodes[nowX].lch].siz + 1; } else { nowX = nodes[nowX].lch; } } }
|
前驱
很显然,让要找的数成为Y树的第一个,然后X树最后一个即为前驱。
1 2 3 4 5
| long long pre(long long nowX) { split(rot, nowX-1, X, Y); return Kth(X, nodes[X].siz); }
|
后缀
同样思路,让要找的数成为X树的最后一个,然后Y树第一个即为前驱。
1 2 3 4 5
| long long nxt(long long nowX) { split(rot, nowX, X, Y); return Kth(Y, 1); }
|
注意!
我们的前驱,后缀操作,都把树拆开了,调用后需要把X树和Y树复原回去。
1
| #define mrg() merge(X,Y)
|
还有,我们找点的操作,返回的都是节点编号,输出时需要翻译成节点权值。
完整代码
好了,这就讲完了!是不是很轻松!
你可别说不轻松哦(鍾城 曉)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
| #include <bits/stdc++.h> #define INF 999999999 #define arand() rand() % 114514 #define mrg() rot = merge(X, Y)
using namespace std;
inline long long read() { long long x = 0; int f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); } return x * f; } void write(const long long &x) { if (!x) { putchar('0'); return; } char f[100]; long long tmp = x; if (tmp < 0) { tmp = -tmp; putchar('-'); } long long s = 0; while (tmp > 0) { f[s++] = tmp % 10 + '0'; tmp /= 10; } while (s > 0) { putchar(f[--s]); } }
const long long maxN = 100090;
struct Node { long long lch, rch; long long val, siz; long long rnd; } nodes[maxN];
long long X, Y, Z; long long rot; long long cnt; long long totN;
void update(long long nowX) { nodes[nowX].siz = nodes[nodes[nowX].lch].siz + nodes[nodes[nowX].rch].siz + 1; }
long long merge(long long Atree, long long Btree) { if ((!Atree) || (!Btree)) { return Atree + Btree; } else if (nodes[Atree].rnd < nodes[Btree].rnd) { nodes[Atree].rch = merge(nodes[Atree].rch, Btree); update(Atree); return Atree; } else { nodes[Btree].lch = merge(Atree, nodes[Btree].lch); update(Btree); return Btree; } }
void split(long long splitX, long long K, long long &Xtree, long long &Ytree) { if (!splitX) { Xtree = Ytree = 0; return ; } else { if (nodes[splitX].val <= K) { Xtree = splitX; split(nodes[splitX].rch, K, nodes[splitX].rch, Ytree); } else { Ytree = splitX; split(nodes[splitX].lch, K, Xtree, nodes[splitX].lch); } update(splitX); } } long long Kth(long long KX, long long K) { while (1) { if (K <= nodes[nodes[KX].lch].siz) { KX = nodes[KX].lch; } else if (K == nodes[nodes[KX].lch].siz + 1) { return KX; } else { K -= nodes[nodes[KX].lch].siz + 1; KX = nodes[KX].rch; } } }
void del(long long delX) { split(rot, delX, X, Z); split(X, delX - 1, X, Y); Y = merge(nodes[Y].lch, nodes[Y].rch); rot = merge(merge(X, Y), Z); }
void insert(long long nowX) { split(rot, nowX, X, Y); ++cnt; nodes[cnt].lch = nodes[cnt].rch = 0; nodes[cnt].val = nowX; nodes[cnt].siz = 1; nodes[cnt].rnd = arand(); rot = merge(merge(X, cnt), Y); }
long long pre(long long nowX) { split(rot, nowX - 1, X, Y); return Kth(X, nodes[X].siz); } long long nxt(long long nowX) { split(rot, nowX, X, Y); return Kth(Y, 1); } long long thK(long long nowX) { split(rot, nowX - 1, X, Y); return nodes[X].siz + 1; }
int main() { totN = read(); int readX, readY; while (totN--) { readX = read(); readY = read(); if (readX == 1) { insert(readY); } else if (readX == 2) { del(readY); } else if (readX == 3) { write(thK(readY)); putchar('\n'); mrg(); } else if (readX == 4) { write(nodes[Kth(rot, readY)].val); putchar('\n'); } else if (readX == 5) { write(nodes[pre(readY)].val); putchar('\n'); mrg(); } else if (readX == 6) { write(nodes[nxt(readY)].val); putchar('\n'); mrg(); } } return 0; }
|
练习
顺便热烈庆祝停课!
好耶!ヽ(✿゚▽゚)ノ
还有
祝贺微软推出新一代操作系统$\text{Windows 11}$。
从此,x86与ARM对峙正式形成!