字典树 Trie
普通 Trie
模板:
namespace trie {
template <size_t _val_max>
struct trie {
static constexpr size_t value_max = _val_max;// 字符集为 [0,value_max) 内的整数
struct node {
size_t size;
size_t count;
size_t father;
array<size_t, value_max> children;
node() : size{}, count{}, father{}, children{} {}
};
vector<node> tree;
trie() : tree{} {
tree.emplace_back();
}
void clear(){
tree.clear();
tree.emplace_back();
}
size_t size(){
return tree[0].size;
}
size_t insert(const basic_string<size_t> &str){
size_t nw = 0;
++tree[nw].size;
for (size_t c : str){
if (!tree[nw].children[c])
tree[nw].children[c] = tree.size(), tree.emplace_back(), tree.back().father = nw;
nw = tree[nw].children[c]; ++tree[nw].size;
}
++tree[nw].count;
return nw;
}
size_t find(const basic_string<size_t> &str){
size_t nw = 0;
for (size_t c : str){
if (!tree[nw].children[c])return 0;
nw = tree[nw].children[c];
}
return nw;
}
size_t count(size_t id){
return tree[id].count;
}
size_t count_all(size_t id){
return tree[id].size;
}
void uncheck_remove(const basic_string<size_t> &str){
size_t nw = 0;
--tree[nw].size;
for (size_t c : str)
nw = tree[nw].children[c], --tree[nw].size;
--tree[nw].count;
}
size_t remove(size_t nw){
size_t ret = tree[nw].count;
tree[nw].count = 0;
for (; nw; nw = tree[nw].father)tree[nw].size -= ret;
tree[nw].size -= ret;
return ret;
}
void _clear(size_t nw){
tree[nw].size = tree[nw].count = 0;
for (size_t i : tree[nw].children)if (i)_clear(i);
}
size_t remove_all(size_t nw){
size_t ret = tree[nw].size;
_clear(nw);
for (; nw; nw = tree[nw].father, tree[nw].size -= ret);
return ret;
}
size_t next(size_t now, size_t ch){
return tree[now].children[ch];
}
template <typename _Func_t>
void for_each(_Func_t &&func){
size_t nw = 0;
basic_string<size_t> str;
do {
bg:
if (tree[nw].count)func(str, tree[nw].count);
size_t c = 0;
while (1){
for (; c < _val_max; ++c)
if (tree[nw].children[c]){str.push_back(c), nw = tree[nw].children[c];goto bg;}
if (c == _val_max){if (!nw)return;c = str.back() + 1, str.pop_back(), nw = tree[nw].father;}
}
} while (1);
}
};
}//namespace trie
建立 \(n\) 个字符串 \(s_{1\sim n}\) 的 \(\operatorname{Trie}\) 的时空复杂度都是 \(O(\sum s_i|\sum|)\)
可通过合并二度点将空间复杂度优化到 \(O(n+\sum s_i)\),称为压缩 \(\operatorname{Trie}\)
\(\operatorname{Trie}\) 的本质是一个接受且仅接受给定串的自动机
其先序遍历相当于将所有插入的字符串排序
0/1 Trie
模板:
namespace trie {
template <typename _Tp, size_t __cnt_bit>
struct bintrie {
typedef _Tp value_type;
static constexpr size_t cnt_bit = __cnt_bit;
struct node {
size_t size;
array<size_t, 2> next;
node() : size{}, next{} {}
};
vector<node> tree;
bintrie() : tree{} {tree.emplace_back();}
void clear(){tree.clear();tree.emplace_back();}
size_t size(){return tree[0].size;}
size_t insert(const value_type &val){
size_t nw = 0;
++tree[nw].size;
for (size_t i = cnt_bit - 1; ~i; --i){
size_t &rf = tree[i].next[val >> i & 1];
if (!rf)nw = rf = tree.size(), tree.emplace_back();
else nw = rf;
++tree[nw].size;
}
return nw;
}
size_t find(const value_type &val){
size_t nw = 0;
for (size_t i = cnt_bit - 1; ~i; --i){
nw = tree[i].next[val >> i & 1];
if (!nw)return 0;
}
return nw;
}
size_t count_all(size_t id){
return tree[id].size;
}
void uncheck_remove(const value_type &val){
size_t nw = 0;
--tree[nw].size;
for (size_t i = cnt_bit - 1; ~i; --i)
--tree[nw = tree[i].next[val >> i & 1]].size;
}
size_t next(size_t now, bool bit){return tree[now].next[bit];}
value_type min_xor(const value_type &a){// return min(a\oplus b | b in trie)
if (tree[0].size == 0)return value_type{};
value_type ret{};
size_t nw = 0;
for (size_t i = cnt_bit - 1; ~i; --i){
size_t ts = tree[i].next[a >> i & 1];
if (ts && tree[ts].size)nw = ts;
else nw = tree[i].next[(a >> i & 1) ^ 1], ret |= 1ull << i;
}
return ret;
}
value_type max_xor(const value_type &a){// return max(a\oplus b | b in trie)
if (tree[0].size == 0)return value_type{};
value_type ret{};
size_t nw = 0;
for (size_t i = cnt_bit - 1; ~i; --i){
size_t ts = tree[i].next[(a >> i & 1) ^ 1];
if (ts && tree[ts].size)nw = ts, ret |= 1ull << i;
else nw = tree[i].next[a >> i & 1];
}
return ret;
}
size_t count_xor_with_less(const value_type &xor_val, const value_type &compare_val){
//return count(xor_val \oplus b < compare_val | b in trie)
size_t ret = 0;
size_t nw = 0;
for (size_t i = cnt_bit - 1; ~i; --i){
size_t ts = xor_val >> i & 1;
if (compare_val >> i & 1)// 0/1
ret += tree[i].next[ts]? tree[tree[i].next[0]].size : (size_t)0, nw = tree[i].next[ts ^ 1];
else nw = tree[i].next[ts];
if (!nw)return ret;
}
return ret;
}
};
}//namespace trie
维护异或和的 0/1 Trie
模板:
namespace trie {
template <typename _Tp, size_t __cnt_bit>
struct bintrie_xorsum {
typedef _Tp value_type;
static constexpr size_t cnt_bit = __cnt_bit;
struct node {
size_t size;
size_t xor_sum;
array<size_t, 2> next;
node() : size{}, xor_sum{}, next{} {}
};
vector<node> tree;
bintrie_xorsum() : tree{}{
tree.emplace_back();
}
void clear(){
tree.clear();
tree.emplace_back();
}
void push_up(size_t k){
tree[k].size = 1;
tree[k].xor_sum = 0;
for (size_t i : tree[k].next)
if (i){
tree[k].size += tree[i].size;
tree[k].xor_sum ^= tree[i].xor_sum;
}
}
size_t insert(const value_type &val){
size_t nw = 0;
++tree[nw].size;
tree[nw].xor_sum ^= val;
for (size_t i = 0; i < cnt_bit; ++i){
size_t &rf = tree[i].next[val >> i & 1];
if (!rf)nw = rf = tree.size(), tree.emplace_back();
else nw = rf;
++tree[nw].size; tree[nw].xor_sum ^= val;
}
return nw;
}
size_t find(const value_type &val){
size_t nw = 0;
for (size_t i = 0; i < cnt_bit; ++i){
nw = tree[i].next[val >> i & 1];
if (!nw)return 0;
}
return nw;
}
size_t count_all(size_t id){
return tree[id].size;
}
void uncheck_remove(const value_type &val){
size_t nw = 0;
--tree[nw].size;
tree[nw].xor_sum ^= val;
for (size_t i = 0; i < cnt_bit; ++i)
--tree[nw = tree[i].next[val >> i & 1]].size, tree[nw].xor_sum ^= val;
}
void increase_all(size_t nw = 0){
std::swap(tree[nw].next[0], tree[nw].next[1]);
if (tree[nw].next[0])increase_all(tree[nw].next[0]);
push_up(nw);
}
value_type xor_sum(size_t id = 0){
return tree[id].xor_sum;
}
};
}//namespace trie
例:P6965 [NEERC2016] Binary Code
给定 \(n\) 个 \(0/1\) 串,每串至多一位未知,可以填 \(0\) 或 \(1\),求是否存在一种方案,使得每个字符串都不是其它任意一个字符串的前缀。字符串数量 \(\le10^5\),总长 \(\le5\times10^5\)
显然是 \(2-SAT\) 问题
但直接建边为 \(O(n^2)\) 的,因此考虑优化
在建边的过程中,\(u\rightarrow v\) 表示若选了 \(u\),则必须选 \(v\)
考虑将 \(2n\) 个字符串(\(?\) 取 \(0\) 和 \(1\) 时作为两个不同的字符串,分别有取/不取的状态,两者之间连边,以下不再重复)插入 \(\operatorname{Trie}\)
以下称结点为 \(\operatorname{Trie}\) 上的点,称关键点为存在字符串结尾的结点
令 \(x\) 表示取 \(S_x\),\(\lnot x\) 表示不取 \(S_x\)
则第一类边为 \(x\rightarrow {\lnot y}\)(\(y\) 为 \(\operatorname{Trie}\) 上 \(x\) 的祖先或 \(x\) 的儿子)
同一个关键点可能有多个字符串结尾,因此第二类边为 若取了其中一个,则另外全部不能取,即每个字符串连向同一关键点中的其它点
可以使用前缀和优化建图和后缀和优化建图实现
时间复杂度 \(O(\sum |s|)\),常数极大

浙公网安备 33010602011771号