Appearance
根号叉树。
Sqrt Tree
Sqrt Tree 可以
考虑序列分块,将原序列分成
不妨将询问区间在同一块内的情况继续递归下去,若每次询问区间不在同一块内,都是
因为每次递归分块长度都是
考虑将这个递归的过程建成一棵树,每个节点都有
那么递归的过程就是自下而上找到第一层存在完全包含于询问区间的块,这个过程可以二分,此时时间复杂度降至
通过调整块长还可继续优化,假设每一层块长都为
也就是唯一满足 (l - 1) ^ (r - 1)。
关于块长
实际维护的时候,不用显式建树,只要维护若干层的分块即可。
点击展开代码
cpp
struct SqrtTree
{
// op 必须满足结合律;代码始终按区间从左到右的顺序合并,
// 因此不要求 op 满足交换律或幂等性。
static inline int op(int x, int y)
{
return max(x, y);
}
struct Layer
{
int n = 0;
int k = 0;
int S = 0;
int block_cnt = 0;
bool top_layer = false;
vector<int> pre, suf;
vector<int> between;
inline int get_between(int lb, int rb) const
{
if (top_layer)
{
return between[lb * block_cnt + rb];
}
else
{
int g = lb >> k;
int li = lb & (S - 1);
int ri = rb & (S - 1);
return between[g * S * S + li * S + ri];
}
}
void build(int _k, int _n, const vector<int> &a, bool _top_layer)
{
k = _k;
n = _n;
S = 1 << k;
top_layer = _top_layer;
block_cnt = (n + S - 1) >> k;
pre.assign(n + 1, 0);
suf.assign(n + 1, 0);
vector<int> whole(block_cnt);
for (int b = 0; b < block_cnt; b++)
{
int L = (b << k) + 1;
int R = min(n, (b + 1) << k);
pre[L] = a[L];
for (int i = L + 1; i <= R; i++)
pre[i] = SqrtTree::op(pre[i - 1], a[i]);
suf[R] = a[R];
for (int i = R - 1; i >= L; i--)
suf[i] = SqrtTree::op(a[i], suf[i + 1]);
whole[b] = pre[R];
}
if (top_layer)
{
between.assign(block_cnt * block_cnt, 0);
for (int i = 0; i < block_cnt; i++)
{
int cur = whole[i];
between[i * block_cnt + i] = cur;
for (int j = i + 1; j < block_cnt; j++)
{
cur = SqrtTree::op(cur, whole[j]);
between[i * block_cnt + j] = cur;
}
}
}
else
{
int group_cnt = (block_cnt + S - 1) >> k;
between.assign(group_cnt * S * S, 0);
for (int g = 0; g < group_cnt; g++)
{
int start = g * S;
int end = min(block_cnt - 1, start + S - 1);
for (int i = start; i <= end; i++)
{
int li = i - start;
int cur = whole[i];
between[g * S * S + li * S + li] = cur;
for (int j = i + 1; j <= end; j++)
{
cur = SqrtTree::op(cur, whole[j]);
int lj = j - start;
between[g * S * S + li * S + lj] = cur;
}
}
}
}
}
inline int query(int l, int r) const
{
int bl = (l - 1) >> k;
int br = (r - 1) >> k;
int ans = suf[l];
if (bl + 1 <= br - 1)
ans = SqrtTree::op(ans, get_between(bl + 1, br - 1));
ans = SqrtTree::op(ans, pre[r]);
return ans;
}
};
int orig_n = 0;
int n = 0;
vector<int> a;
vector<Layer> layer;
vector<short> which;
static inline int highest_bit(int x)
{
return x == 0 ? 0 : 31 - __builtin_clz((unsigned)x);
}
void build(const vector<int> &src, int _n)
{
// src 使用 1 下标,合法元素为 src[1.._n]。
assert(_n >= 1 && _n < (int)src.size());
orig_n = _n;
layer.clear();
which.clear();
n = 1;
while (n < orig_n)
n <<= 1;
a.assign(n + 1, 0);
for (int i = 1; i <= orig_n; i++)
a[i] = src[i];
// 补到 2 的幂。正常询问不会访问补出来的位置。
// 对本题 max 来说,补什么都不影响合法询问。
for (int i = orig_n + 1; i <= n; i++)
a[i] = src[orig_n];
if (n <= 2)
return;
int lgN = __lg(n);
// top_k 是不超过 log2(n) 的最大 2 的幂。
// 例如 n = 2^17,则 top_k = 16。
int top_k = 1;
while ((top_k << 1) <= lgN)
top_k <<= 1;
vector<int> ks;
for (int k = top_k; k >= 1; k >>= 1)
ks.push_back(k);
layer.reserve(ks.size());
for (int i = 0; i < (int)ks.size(); i++)
{
layer.emplace_back();
layer.back().build(ks[i], n, a, i == 0);
}
which.assign(n, 0);
for (int mask = 1; mask < n; mask++)
{
int hb = highest_bit(mask);
int id = 0;
while (id + 1 < (int)ks.size() && ks[id] > hb)
id++;
which[mask] = (short)id;
}
}
inline int query(int l, int r) const
{
if (l == r)
return a[l];
if (r == l + 1)
return op(a[l], a[r]);
int mask = (l - 1) ^ (r - 1);
int id = which[mask];
return layer[id].query(l, r);
}
};可以发现,Sqrt Tree 维护信息的本质是预处理首尾前后缀和中间整块信息。合并运算必须满足结合律;若运算不满足交换律,查询时必须严格按“左侧后缀、中间整块、右侧前缀”的顺序合并。Sqrt Tree 与猫树(Disjoint Sparse Table)都能处理任意结合运算,不要求幂等性,二者不存在功能上的严格包含关系;经典的重叠 Sparse Table 则通常要求运算幂等。它们的主要差别在预处理层数、空间布局和实现常数,不能笼统写成 Sqrt Tree > 猫树 > ST 表。
上面的参考实现是静态结构,只提供 build 和 query,不支持修改。某些 Sqrt Tree 变体可以通过重建受影响的块实现单点修改或区间赋值,但复杂度取决于具体分层、重建范围和标记设计,不能把某一变体的修改复杂度直接当作当前模板的能力。
vEB
Sqrt Tree 是序列上的根号树,vEB 是对值域做根号树。
vEB 功能上与压位 Trie 相同,用于维护插入、删除、前驱、后继、最大值、最小值。
vEB 上每个节点将值域分成
每个节点上这
值域根号
空间复杂度:
插入
如果当前位置为空,直接令最小值和最大值都等于
若
随后把 summary。若
时间复杂度:
删除
如果当前节点表示的值域大小
如果删除的值为最大值/最小值,需要重新计算子树中的最大值/最小值,找到最大/最小的存在值的子树,返回起最大/最小值即可。
反之,找到
时间复杂度:
后继/前驱
如果和查询的
反之,找到高位/低位第一个存在值的子树,相当于在维护子节点的那个 vEB 中查询后继/前驱,这也是一个递归的过程。
找到那个第一个存在值的子树后,直接返回它维护的最小值/最大值即可。
时间复杂度:
最大值/最小值
全局的最大最小值是在根节点直接维护的,直接获取即可。
时间复杂度:
点击展开代码
cpp
struct vEB
{
struct VEB
{
static constexpr int BASE_BITS = 6;
int bits;
int low_bits, high_bits;
int mn, mx;
unsigned long long mask;
VEB *summary;
VEB **child;
VEB(int b = 20)
: bits(b),
low_bits(0),
high_bits(0),
mn(-1),
mx(-1),
mask(0),
summary(nullptr),
child(nullptr)
{
if (bits > BASE_BITS)
{
low_bits = bits >> 1;
high_bits = bits - low_bits;
}
}
bool is_base() const
{
return bits <= BASE_BITS;
}
bool empty() const
{
return mn == -1;
}
int high(int x) const
{
return x >> low_bits;
}
int low(int x) const
{
return x & ((1 << low_bits) - 1);
}
int idx(int h, int l) const
{
return (h << low_bits) | l;
}
int get_min() const
{
return mn;
}
int get_max() const
{
return mx;
}
void pull_base()
{
if (mask == 0)
{
mn = mx = -1;
}
else
{
mn = __builtin_ctzll(mask);
mx = 63 - __builtin_clzll(mask);
}
}
void ensure_child_array()
{
if (child == nullptr)
{
child = new VEB *[1 << high_bits]();
}
}
VEB *ensure_child(int h)
{
ensure_child_array();
if (child[h] == nullptr)
{
child[h] = new VEB(low_bits);
}
return child[h];
}
VEB *get_child(int h) const
{
return child == nullptr ? nullptr : child[h];
}
VEB *ensure_summary()
{
if (summary == nullptr)
{
summary = new VEB(high_bits);
}
return summary;
}
bool contains(int x) const
{
if (mn == -1)
return false;
if (x == mn || x == mx)
return true;
if (x < mn || x > mx)
return false;
if (is_base())
{
return (mask >> x) & 1ULL;
}
int h = high(x);
int l = low(x);
VEB *c = get_child(h);
return c != nullptr && c->contains(l);
}
void insert(int x)
{
if (is_base())
{
mask |= (1ULL << x);
pull_base();
return;
}
if (mn == -1)
{
mn = mx = x;
return;
}
if (x == mn || x == mx)
return;
if (x < mn)
{
int t = x;
x = mn;
mn = t;
}
int h = high(x);
int l = low(x);
VEB *c = ensure_child(h);
if (c->empty())
{
ensure_summary()->insert(h);
}
c->insert(l);
if (x > mx)
{
mx = x;
}
}
void erase(int x)
{
if (is_base())
{
mask &= ~(1ULL << x);
pull_base();
return;
}
if (mn == -1 || x < mn || x > mx)
return;
if (x != mn && x != mx)
{
int h0 = high(x);
int l0 = low(x);
VEB *c0 = get_child(h0);
if (c0 == nullptr || !c0->contains(l0))
return;
}
if (mn == mx)
{
mn = mx = -1;
return;
}
if (x == mn)
{
int first_cluster = summary->get_min();
VEB *c = child[first_cluster];
int new_low = c->get_min();
x = idx(first_cluster, new_low);
mn = x;
}
int h = high(x);
int l = low(x);
VEB *c = child[h];
c->erase(l);
if (c->empty())
{
summary->erase(h);
if (x == mx)
{
int last_cluster = summary->get_max();
if (last_cluster == -1)
{
mx = mn;
}
else
{
mx = idx(last_cluster, child[last_cluster]->get_max());
}
}
}
else if (x == mx)
{
mx = idx(h, c->get_max());
}
}
int prev(int x) const
{
if (mn == -1)
return -1;
if (is_base())
{
if (x <= 0)
return -1;
unsigned long long m;
if (x >= 64)
{
m = mask;
}
else
{
m = mask & ((1ULL << x) - 1ULL);
}
if (m == 0)
return -1;
return 63 - __builtin_clzll(m);
}
if (x <= mn)
return -1;
if (x > mx)
return mx;
int h = high(x);
int l = low(x);
VEB *c = get_child(h);
if (c != nullptr)
{
int p = c->prev(l);
if (p != -1)
{
return idx(h, p);
}
}
int pc = summary == nullptr ? -1 : summary->prev(h);
if (pc == -1)
{
return mn;
}
return idx(pc, child[pc]->get_max());
}
int next(int x) const
{
if (mn == -1)
return -1;
if (is_base())
{
if (x < 0)
return get_min();
if (x >= 63)
return -1;
unsigned long long m = mask & (~0ULL << (x + 1));
if (m == 0)
return -1;
return __builtin_ctzll(m);
}
if (x < mn)
return mn;
if (x >= mx)
return -1;
int h = high(x);
int l = low(x);
VEB *c = get_child(h);
if (c != nullptr)
{
int s = c->next(l);
if (s != -1)
{
return idx(h, s);
}
}
int sc = summary == nullptr ? -1 : summary->next(h);
if (sc == -1)
{
return mx;
}
return idx(sc, child[sc]->get_min());
}
};
static constexpr int UNIVERSE_BITS = 20;
static constexpr int UNIVERSE_SIZE = 1 << UNIVERSE_BITS;
VEB root;
vEB() : root(UNIVERSE_BITS) {}
static bool in_domain(int x)
{
return 0 <= x && x < UNIVERSE_SIZE;
}
void insert(int x)
{
assert(in_domain(x));
root.insert(x);
}
void erase(int x)
{
assert(in_domain(x));
root.erase(x);
}
bool contains(int x) const
{
return in_domain(x) && root.contains(x);
}
static std::optional<int> to_optional(int value)
{
if (value == -1)
return std::nullopt;
return value;
}
std::optional<int> get_min() const
{
return to_optional(root.get_min());
}
std::optional<int> get_max() const
{
return to_optional(root.get_max());
}
std::optional<int> get_prev(int x) const
{
assert(in_domain(x));
return to_optional(root.prev(x));
}
std::optional<int> get_next(int x) const
{
assert(in_domain(x));
return to_optional(root.next(x));
}
};这份实现的合法值域是 -1 表示空节点,公开的最小值、最大值、前驱和后继接口返回 std::optional<int>;std::nullopt 表示不存在,因此合法值 0 不再与“无解”混淆。使用代码时需要包含 <optional>。
