Sparse Tableを今更学んだので、メモを兼ねて書きます。
Sparse Tableとは
不変な数列の任意の区間に対する最小値/最大値を、前処理 $O(N\log N)$, クエリごと $O(1)$ で求めるデータ構造です。
数列の値が変わる可能性がある場合はSegmentTreeを使いましょう。SegmentTreeの記事はこちらです。
仕組み
すべての点を始点として、長さが2べきの区間の最小値/最大値を先に求めておきます。これが $O(N\log N)$ の前計算です。
クエリに答えるときは、区間の長さ以下で最大の2べきの値をとり、区間の前側と後ろ側にはめてそれぞれの最小値を求めます。
これで区間全体を覆うことができ、最小値が求まります。
テーブルを構築すると、こんな感じになります。
区間の長さから、それ以下の2べきを求めるのも前計算に含めておくと、クエリごとに $O(1)$ になります。
実装
template<typename T>
class SparseTable {
T** table;
int* logtable;
public:
SparseTable(vector<T> vec) {
int maxlength = 0;
while ((1 << (maxlength + 1)) <= vec.size())maxlength++;
table = new T * [maxlength + 1];
logtable = new int[vec.size() + 1];
rep(i, maxlength + 1) {
table[i] = new T[vec.size()];
rep(j, vec.size() - (1 << i) + 1) {
if (i)table[i][j] = min(table[i - 1][j], table[i - 1][j + (1 << (i - 1))]);
else table[i][j] = vec[j];
}
}
for (int i = 2; i <= vec.size(); i++) {
logtable[i] = logtable[i >> 1] + 1;
}
}
~SparseTable() {
rep(i, maxlength) delete[] table[i];
delete[] table;
delete[] logtable;
}
T query(int l, int r) {
assert(l < r);
int length = r - l;
return min(table[logtable[length]][l], table[logtable[length]][r - (1 << logtable[length])]);
}
};
かなり雑で可読性が低いです。ごめんなさい。
おわりに
Segment Treeのほうが自分としては書くのが楽ですが、Sparse Tableは定数倍がかなり軽いです。
Sparse Tableもぱっと書けるようにしておくといいかもしれません。(使ったことは、ありません)