【学习笔记】压位 Trie

· · 算法·理论

推荐阅读

板子链接

事实上,压位 Trie 已经算一个比较偏的知识点了,便得 OI-wiki 上都没有。在实际应用中,除了不完全优化掉一个 \log 之外没有任何用处。

引入

首先,板子的操作可以规约为以下问题:

有一个比较 ez 的做法是直接上 01Trie,从而做到单次操作 O(\log_2V) 的时间复杂度。

思考一下为什么这个做法很慢?原因在于 01Trie 每层只有两个分叉,导致层数过深。

那么我们只需要让每层有 w 个分叉即可,这样我们就能做到单次操作 O(\log_wV) 的时间复杂度,这就是压位 Trie 的核心思想。

容易发现,压位 Trie 是一种亚 log 数据结构。

结构

与普通的 01Trie 不同,压位 Trie 的结构是静态的,如下图:

上图是一个四叉的压位 Trie,容易发现其深度就只有 O(\log_wV),其中 w=4

其中每个节点维护的是在该位置是否有值,标深色表示有值,每个节点旁边的数字表示其每个儿子是否有值,叶子节点表示每个数。

实际上因为有 `unsgiend long long`,所以大部分的实现都是 $64$ 叉树,从而有 $w=64$,复杂度为 $\log_w V$。 实际实现时似乎把最后一层补满更好? ### 空间复杂度 直接存显然是 $O(V)$ 的。 容易发现我们只需要存每个节点旁边的信息,这样也能表示一棵压位 Trie。那么最后一层就没必要存了,从而得到 $O(\dfrac{V}{w})$ 的空间复杂度。 ### 扩展 显然我们在压位 Trie 上可以不止维护每个节点是否有值,还可以维护其它的信息,从而查询更多的问题。 ### 具体实现 **压位 Trie 的实现一般都是自底向上的。** 因为这样方便我们剪枝。 - 插入时,如果这个点的父亲已经有这个儿子的信息了,那么它的父亲已经存在了,就不用继续往上算了。 - 删除时,如果这个点的儿子没删完,那么往上都仍然是有信息的,就不用算了。 - 查询前驱后继时,显然让尽量多的位和 $x$ 相同一定更优。 同时,让层数更小的更深,可以简化位运算难度。 具体实现见代码。 ### 插入/删除 简单地说就是自底向上更新每个节点的信息,如果信息不变,那么就不需要继续更新了。 ### 前驱/后继 显然我们可以通过简单的位运算算出每一层是否有比这个值更大/更小的兄弟,然后钦定前面的每一位都相等,然后再在后面直接取最大/最小即可。 需要使用 `__builtin_clzll` 和 `__builtin_ctzll`。 ### 实现 用的是 `vector` 的 `resize`,好像比指针更慢? ```cpp #include<bits/stdc++.h> #include"trie.h" #define ull unsigned long long using namespace std; struct _Trie{ const int g=6,mod=63; vector<ull> a[5]; int dep; void build(int sz=1<<30){ dep=1; for(;;++dep){ int cnt=(sz>>g*dep); a[dep-1].clear(),a[dep-1].shrink_to_fit(); a[dep-1].resize(cnt); if(cnt==1) return; } } void ins(int x){ for(int i=0;i<dep;++i){ ull p=1ull<<(x>>i*g&mod); if(a[i][x>>(i+1)*g]&p) return; a[i][x>>(i+1)*g]|=p; } } void del(int x){ for(int i=0;i<dep;++i) if(a[i][x>>(i+1)*g]&=~(1ull<<(x>>i*g&mod))) return; } int pre(int x){ for(int i=0;i<dep;++i){ int cur=(x>>i*g&mod); ull state=a[i][x>>(i+1)*g]; if(state&((1ull<<cur)-1)){ int res=x>>(i+1)*g<<(i+1)*g; res+=(mod-__builtin_clzll(state&((1ull<<cur)-1)))<<i*g; for(int j=i-1;~j;--j) res+=(mod-__builtin_clzll(a[j][res>>(j+1)*g]))<<j*g; return res; } } return 0; } int suf(int x){ for(int i=0;i<dep;++i){ int cur=(x>>i*g&mod); ull state=a[i][x>>(i+1)*g]; if(state>>cur>1){ int res=x>>(i+1)*g<<(i+1)*g; res+=(__builtin_ctzll(state>>(cur+1))+cur+1)<<i*g; for(int j=i-1;~j;--j) res+=__builtin_ctzll(a[j][res>>(j+1)*g])<<j*g; return res; } } return 0; } }T; bool a[200000001]; void init(int n){ T.build(); } void assign(int i,int x){ if(a[i]==x) return; if(a[i]) T.del(i); else T.ins(i); a[i]=x; } int nxt(int i){ return T.suf(i); } int pre(int i){ return T.pre(i); } ``` ### 实操 - [[JSOI2009] 面试的考验 ](https://www.luogu.com.cn/problem/P5926) - [CF765F Souvenirs](https://www.luogu.com.cn/problem/CF765F) - [[ZJOI2019] 语言](https://www.luogu.com.cn/problem/P5327)