差不多。我以前没见过这个名字,但代码看起来和我见过的人们称之为population tree的代码一样。
人口树是使用分段树的一种简单方法。你有 N 个元素,每个元素都有两种可能的状态:1 表示活着,0 表示死亡。树支持两种操作:
- 切换编号为 i 的元素的状态。
- 返回第 k-th 活元素的索引(以其索引的大小)。
为了阐明第二个操作,假设一组活元素是 {1, 2, 4, 7}。
如果 N = 8,则对应状态数组 01101001(元素 0 已死亡,元素 1 处于活动状态,元素 3 处于活动状态,依此类推)。
那么,如何实现呢?
像往常一样,树的叶子对应于 array。也就是说,如果第 i 个元素是活动的,则第 i 个叶子的值为 1,否则为 0。
每个内部节点都会记住其子节点的值之和。
为了切换元素的状态,我们切换相应叶子的值,然后固定从该叶子到根的路径:
const int size = 1 << 18; // 2^17 elements, the rest are internal nodes
int tree[size]; // number of living elements in the subtree of a node
void toggle(int i) {
tree[i + size / 2] ^= 1; // toggle the leaf
for (i /= 2; i > 0; i /= 2)
tree[i] = tree[2 * i] + tree[2 * i + 1];
}
注意:标记节点的常用方法是使 root 等于 1,并递归地,
节点 n 的子节点是 2n 和 2n+1。
要找到第 k-th 个生活元素,我们可以递归思考:
我们目前在节点 n 处,并且正在寻找其子树的第 k-th 个活元素(节点的子树是根于该节点的树)。
如果n的左孩子2n有k或更多的活元素,设置n = 2n。
否则,我们显然会找到正确的孩子,即设置 n = 2n+1。
在这种情况下,我们不再寻找子树的第 k-th 个活元素。
因为我们跳过了左孩子的所有生活元素,所以我们从 k 中减去该计数。为简单起见,我们在此查看基于 1 的生活元素。
这里的基本思想可能是递归的,但描述暗示迭代地做它也应该很简单:
int kth(int k) {
++k; // because this method looks at elements 1-based
int current_node = 1; // start at the root
while (current_node < size / 2) {
if (tree[2 * current_node] >= k)
current_node = 2 * current_node; // descend into the left child
else {
k -= tree[2 * current_node]; // fix k
current_node = 2 * current_node + 1; // descend into the right child
}
}
}
这两个功能使细分树成为人口树。