0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

競プロ用ライブラリを作るAdvent Calendar 2024

Day 22

22日目: WaveletMatrixを実装する

Last updated at Posted at 2024-12-21

競プロ用ライブラリを作る Advent Calendar 2024の22日目です.

WaveletMatrixって?

整数列に対してだいたいなんでもできるデータ構造です

整数列$x_1,x_2,...x_N$についてこういうクエリが対数時間で処理できます.

  • 指定された$i$について, $x_i$を求める
  • 指定された$a, b, l, r$について, 指定された区間の$x_l, x_{l+1},..., x_r$かつ$a\le x_i\le b$の範囲の数の個数を数える
  • 指定された$l, r, a$について, $x_l, x_{l+1},..., x_r$の中で$a$番目に小さい/大きいものを求める
  • 指定された$a, n$について, $x_i=a$を満たす小さい方/大きい方から$n$番目の$i$を求める

仕組み

完備辞書

「整数列に対してなんでもできるデータ構造」の前に「01列に対してある程度できるデータ構造」を作ります.

01列に対して以下が出来ます.

  • 指定された場所が0か1か求める
  • 指定された$i$について, $i$番目の0の位置を求める
  • 指定された$i$について, $i$番目の1の位置を求める
  • 指定された$i$について, 1番目から$i$番目までの1の個数を求める

まず, bitsetで01列を持ちます. このbitsetを64bit整数の列として表現して, 同じく64個ごとに「それより前に1がいくつあるか」を別に持っておきます.

struct SampleDict {
    // bitset本体
    main: Vec<u64>,
    // 64個ごとに個数を持つ配列
    sub: Vec<usize>,
    // 1の合計の個数
    total: usize,
}
impl SampleDict {
    fn new(bitset: Vec<u64>) -> Self {
        let mut count = 0;
        let mut sub = vec![0];
        for val in &bitset {
            count += val.count_ones() as usize;
            sub.push(count);
        }
        Self {
            main: bitset,
            sub,
            total: count,
        }
    }

    // 要素数を返す
    fn len(&self) -> usize {
        self.main.len() * 64
    }

    // index番目の値が0か1か見る
    fn get(&self, index: usize) -> bool {
        (self.main[index / 64] >> (index % 64)) & 1 == 1
    }

    // 先頭index個に1がいくつあるか数える
    fn rank(&self, index: usize) -> usize {
        // 64個単位の個数
        let a = self.sub[index / 64] as usize;
        // ↑で数え漏らした分の個数
        let b = (self.main[index / 64] & !(!0 << (index % 64))).count_ones() as usize;
        a + b
    }

    // count番目の1がどこにあるか探す
    // rankを二分探索して実装します
    fn select(&self, count: usize) -> usize {
        let mut min = 0;
        let mut max = self.main.len() * 64;
        while max - min > 1 {
            let mid = (min + max) / 2;
            if self.rank(mid) <= count {
                min = mid;
            } else {
                max = mid;
            }
        }
        min
    }
}

頑張ればメモリ消費量を削減したりselectのオーダーを改善したりできるらしいですが, 競プロの範囲ではそんなに問題にならないのでこのままでも大丈夫です.

本体

32bitの非負整数を持つ場合を考えます.

上位bitから順番に「bitが1になっている要素を右に寄せる」という操作を行います.

3 1 4 1 5 9 2 6 5 3 5
3 1 4 1 5 2 6 5 3 5|9  <-- 8の位が1のものを右に寄せる
3 1 1 2 3 9|4 5 6 5 5  <-- 4の位が1のものを右に寄せる
1 1 9 4 5 5 5|3 2 3 6  <-- 2の位が1のものを右に寄せる
4 2 6|1 1 9 5 5 5 3 3  <-- 1の位が1のものを右に寄せる

寄せる前の数列で注目しているbitが0か1かを書きます

0 0 0 0 0 1 0 0 0 0 0
0 0 1 0 1 0 1 1 0 1|0
1 0 0 1 1 0|0 0 1 0 0
1 1 1 0 1 1 1|1 0 1 0

これを1列ずつ完備辞書に乗せたものがWaveletMatrixです.

ここから数字を復元してみます, この01の情報から, どっちに寄せられたか, が分かるのでこれを追います.

        ここの数字を復元する
        v
0 0 0 0 0 1 0 0 0 0 0
        |              0なので, 左に寄せた位置を見る
0 0 1 0 1 0 1 1 0 1|0
        ^-----v        1なので, 右に寄せた位置を見る
1 0 0 1 1 0|0 0 1 0 0
        v-----^        0なので, 左に寄せた位置を見る
1 1 1 0 1 1 1|1 0 1 0
        ^
        01を拾っていくと0101 (10進数で5)

左に寄せた位置を見る場合は「その前にある1の個数だけ前を見る」で, 右に寄せた位置を見る場合は「その後ろにある0の個数だけ後ろを見る」とすればいいです.

fn access(dicts: &[SampleDict; 64], mut index: usize) -> u64 {
    let mut ret = 0;
    for (i, dict) in dicts.iter().enumerate() {
        if dict.get(i) {
            ret |= 1 << (63 - i);
            // その前にある1の個数を消す
            index -= dict.rank(index);
        } else {
            // 後ろにある0の個数を頑張って数えるとこうなります
            index = dict.len() - dict.total - (index - dict.rank(dict.len()));
        }
    }
    ret
}

こういう感じでいろんな機能を付けることが出来ます.

何でもできるのですが, 1つ1つ挙げていくとキリがないので省略します. 知りたい方へはこの記事とかが詳しかったです.

実装

いろいろしました. まだできることは全然あると思います.

まとめ

いかがでしたか?

明日は自明をします.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?