かなり。その名前は初めて見ましたが、コードは人々が人口ツリーと呼んでいるものと同じように見えます。
人口ツリーは、セグメント ツリーを使用する簡単な方法です。N 個の要素があり、それぞれに 2 つの可能な状態があります。1 つは生きている状態、0 は死んでいるものです。ツリーは次の 2 つの操作をサポートします。
- i番号の要素の状態を切り替えます。
- k 番目(そのインデックスのサイズ) の生きている要素
のインデックスを返します。
2 番目の操作を明確にするために、生きている要素のセットが {1, 2, 4, 7} であるとしましょう。N = 8 の場合、これは状態配列 01101001 に対応します (要素 0 は無効、要素 1 は有効、要素 3 は有効、など)。
では、これを実装する方法は?いつものように、ツリーの葉は配列に対応します。つまり、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];
}
注:ノードにラベルを付ける一般的な方法は、ルートを 1 に等しくし、再帰的にノードnの子を2nと2n+1にすることです。
k 番目の生きている要素を見つけるには、再帰的に考えることができます: 現在ノードnにいて、そのサブツリーでk 番目の生きている要素を探しています (ノードのサブツリーは、そのノードをルートとするツリーです)。nの左の子2nにk個以上の生きている要素がある場合、 n = 2nに設定します。それ以外の場合は、明らかに適切な子、つまりn = 2n+1に設定されたものに移動します。その場合、サブツリーのk 番目の要素を探す必要はなくなります。左の子のすべての生きている要素をスキップしたため、そのカウントを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
}
}
}
これら 2 つの関数によって、セグメント ツリーが母集団ツリーになります。