-# 替罪羊树\r
-\r
-**替罪羊树**是一种依靠重构操作维持平衡的重量平衡树。替罪羊树会在插入、删除操作时,检测途经的所有节点,若发现失衡,则将以该节点为根的子树重构。\r
+**替罪羊树**是一种依靠重构操作维持平衡的重量平衡树。替罪羊树会在插入、删除操作时,检测途经的节点,若发现失衡,则将以该节点为根的子树重构。\r
\r
我们在此实现一个可重的权值平衡树。\r
\r
wn[MAXN], // 本数据出现次数(为 0 代表已删除)\r
s[MAXN], // 以本节点为根的子树大小\r
sd[MAXN]; // 已删除节点不计的子树大小\r
+ \r
+void Calc(int k) {\r
+// 重新计算以 k 为根的子树大小\r
+ s[k] = s[lc[k]] + s[rc[k]] + wn[k];\r
+ sd[k] = sd[lc[k]] + sd[rc[k]] + wn[k];\r
+}\r
```\r
\r
## 重构\r
\r
-首先,如前所述,我们需要判定一个节点是否需要重构。为此我们引入一个比例常数 $\alpha$(取值在 $$(0.5,1)),如某节点的子节点大小占它本身大小的比例超过 $\alpha$,则重构。\r
+首先,如前所述,我们需要判定一个节点是否应重构。为此我们引入一个比例常数 $\alpha$(取值在 $(0.5,1)$,一般采用 $0.7$ 或 $0.8$),若某节点的子节点大小占它本身大小的比例超过 $\alpha$,则重构。\r
\r
-另外由于我们采用惰性删除(删除只使用 `wn[i]--`),已删除节点过多也影响效率。因此如果未被删除的子树大小占子树总大小的比例低于 $\alpha$,则亦重构。\r
+另外由于我们采用惰性删除(删除只使用 `wn[k]--`),已删除节点过多也影响效率。因此若未被删除的子树大小占总大小的比例低于 $\alpha$,则亦重构。\r
\r
``` cpp\r
inline bool CanRbu(int k) {\r
// 判断节点 k 是否需要重构\r
return wn[k] && (alpha * s[k] <= (double)std::max(s[lc[k]], s[rc[k]])\r
- || alpha * s[k] >= (double)sd[k]);\r
+ || (double)sd[k] <= alpha * s[k]);\r
}\r
```\r
\r
}\r
\r
int Rbu_Build(int l, int r) {\r
-// 将数组内 [l, r) 区间重建成树\r
- int mid = l + r >> 1;\r
+// 将 ldr[] 数组内 [l, r) 区间重建成树,返回根节点\r
+ int mid = l + r >> 1; // 选取中间为根使其平衡\r
if(l >= r) return 0;\r
lc[ldr[mid]] = Rbu_Build(l, mid);\r
- rc[ldr[mid]] = Rbu_Build(mid + 1, r);\r
- if(l + 1 == r) s[ldr[mid]] = sd[ldr[mid]] = wn[ldr[mid]];\r
- else Calc(ldr[mid]);\r
+ rc[ldr[mid]] = Rbu_Build(mid + 1, r); // 建左右子树\r
+ Calc(ldr[mid]);\r
return ldr[mid];\r
}\r
\r
\r
## 基本操作\r
\r
+几种操作的处理方式较为类似,都规定了**到达空结点**与**找到对应结点**的行为,之后按**小于向左、大于向右**的方式向下递归。\r
+\r
### 插入\r
\r
-插入时,从根节点向下寻找插入元素位置。如没有相同元素则新建节点,如有则直接 `wn[k]++`。最后,途经的可重构节点进行重构。\r
+插入时,到达空结点则新建节点,找到对应结点则 `wn[k]++`。递归结束后,途经的节点可重构的要重构。\r
\r
``` cpp\r
void Ins(int& k, int p) {\r
\r
### 删除\r
\r
-æ\83°æ\80§å\88 é\99¤ï¼\8cå\8f¯ä»¥é\87\87ç\94¨ `wn[k]--`ã\80\82å\9c¨æ\9c\80å\90\8e沿é\80\94可重构节点要重构。\r
+æ\83°æ\80§å\88 é\99¤ï¼\8cå\88°è¾¾ç©ºç»\93ç\82¹å\88\99忽ç\95¥ï¼\8cæ\89¾å\88°å¯¹åº\94ç»\93ç\82¹å\88\99 `wn[k]--`ã\80\82é\80\92å½\92ç»\93æ\9d\9få\90\8eï¼\8c可重构节点要重构。\r
\r
``` cpp\r
void Del(int& k, int p) {\r
\r
### upper_bound\r
\r
-返回权值严格大于某值的最小数的名次。每到一个节点,若查找值等于该节点权值,则返回该节点之后的名次。小于则向左走,大于则向右走。\r
+返回权值严格大于某值的最小名次。\r
+\r
+到达空结点则返回 1,因为只有该子树左边的数均小于查找数才会递归至此。找到对应结点,则返回该节点所占据的最后一个名次 + 1。\r
\r
``` cpp\r
int MyUprBd(int k, int p) {\r
else if(p < w[k]) return MyUprBd(lc[k], p);\r
else return sd[lc[k]] + wn[k] + MyUprBd(rc[k], p);\r
}\r
- \r
+```\r
+\r
+以下是反义函数,相当于采用 `std::greater<>` 比较,即返回权值严格小于某值的最大名次。查询一个数的排名可以用 `MyUprGrt(rt, x) + 1`。\r
+\r
+``` cpp\r
int MyUprGrt(int k, int p) {\r
-// 反义的函数,相当于采用 std::greater<> 比较\r
if(!k) return 0;\r
else if(w[k] == p && wn[k]) return sd[lc[k]];\r
else if(w[k] < p) return sd[lc[k]] + wn[k] + MyUprGrt(rc[k], p);\r
\r
### at\r
\r
-ç»\99å®\9aå\90\8d次ï¼\8cè¿\94å\9b\9e该å\90\8d次ä¸\8aç\9a\84æ\9d\83å\80¼ã\80\82å°\86å\90\8d次ä¸\8eè\8a\82ç\82¹å·¦å\90æ \91大å°\8få\8f\8aæ\9c¬è\8a\82ç\82¹é\87\8då¤\8d次æ\95°æ¯\94è¾\83ï¼\8cå\90\8cæ ·å°\8fäº\8eå\90\91å·¦ï¼\8c大äº\8eå\90\91å\8f³。\r
+ç»\99å®\9aå\90\8d次ï¼\8cè¿\94å\9b\9e该å\90\8d次ä¸\8aç\9a\84æ\9d\83å\80¼ã\80\82å\88°è¾¾ç©ºç»\93ç\82¹è¯´æ\98\8eæ\97 æ¤å\90\8d次ï¼\8cæ\89¾å\88°å¯¹åº\94ç»\93ç\82¹å\88\99è¿\94å\9b\9eå\85¶æ\9d\83å\80¼。\r
\r
``` cpp\r
int MyAt(int k, int p) {\r
\r
### 前驱后继\r
\r
-以上两种功的组合。\r
+以上两种功能结合即可。\r
\r
``` cpp\r
inline int MyPre(int k, int p) { return MyAt(k, MyUprGrt(k, p)); }\r