trie树
\(1.\text{trie}\)树的结构和性质
\(\text{trie}\)树,即字典树,其结构特点在于每一条边代表一个字符,一条从根出发的路径代表一个前缀或字符串,利用这个特点,在\(\text{trie}\)上我们可以快速的查找前缀
例如,在上述的树中,\(1\rightarrow 3\)这条路径,代表字符串\(\text{ar},LCA(6,7)=5\rightarrow \text{ca,cp}\)的最长公共前缀是\(\text c\)
注:若在建树时选择了从高位到低位建树,则上述的前缀全部变为后缀
为了实现上述的结构与功能,我们引入以下数组
\(1.nxt[u][p]:\)表示\(u\)号节点的下一个字符编号为\(p\)的节点的编号
\(2.end[u]:\)有多少个字符串以\(u\)节点为结尾
\(3.pass[u]:\)有多少个字符串的路径经过了点\(u\)
于是在\(\text{trie}\)树上,我们支持以下的操作
\(1.\text{insert}\) 用于插入字符串
void insert(string s){
int u=0;
for(int i=0;i<s.size();i++){
int p=getnum(s[i]);//为每一个字符确定一个唯一的编号
if(!nxt[u][p]) nxt[u][p]=++tot;
u=nxt[u][p];
}
exist[u]=1;
}
\(2.\text{search}\) 用于查找字符串是否存在
bool search(string s){
int u=0;
for(int i=0;i<s.size();i++){
int p=getnum(s[i]);
if(!nxt[u][p]) return 0;
u=nxt[u][p];
}
return exist[u];
}
\(3.\text{erase}\) 用于删除某字符串
void erase(string s){
int u=0;
for(int i=0;i<s.size();i++){
int p=getnum(s[i]);
if(!nxt[u][p]) return;
u=nxt[u][p];
}
exist[u]=0;
}
\(2.\text{0-1trie}\)
\(\text{0-1trie}\)就是将一个整数看作\(01\)字符串,并将其插入\(\text{trie}\)树中,其优势在于快速的处理异或有关问题,其有两种建树方式,从高位到低位建树与从低位到高位建树,其分别适用于以下的两种操作
\(1.\)求异或极值
现在我们有一颗\(\text{trie}\)树以及一个值\(x\),我们将\(x\)按二进制位分解后在\(\text{trie}\)树上处理,我们贪心的尽可能走与\(x\)异或后为\(1\)的节点,只要建树方式为从高位到低位,该贪心的正确性显然
int match(int x){
int u=0,ans=0;
for(int i=maxn;i>=0;i--){
int p=(x>>i)&1;
if(!ch[u][p^1]) u=ch[u][p];
else{u=ch[u][p^1];ans+=(1<<i);}
}
return ans;
}
\(2.\)全局加\(1\)
假设\(\text{trie}\)树维护了数组\(a_1,a_2\dotsb,a_n\)得值,我们现在对\(a\)数组全部加一,考虑在\(\text{trie}\)树上造成了什么影响
我们从低位到高位建树,假设现在是第\(p\)位
若该位为\(1\),\(+1\)后变为\(0\)并向高位进一,于是递归高位
若该位为\(0\),\(+1\)后变为\(1\),不做操作
void add(int u){
swap(ch[u][0],ch[u][1]);
if(!ch[u][0]) return;
else add(ch[u][0]);
}
\(3.\)类线段树操作
如果我们选择由低位到高位建树,则\(\text{trie}\)树会具有线段树的某些特性,于是我们也可以用线段树的方式\(\text{(Lazy tag)}\)去处理某些操作
\((1).\)维护区间异或和,我们需要\(\text{maintain}\)操作由\(u\)的左右儿子去维护\(u\)子树内的信息,这里的\(\text{maintain}\)操作类似于线段树的\(\text{pushup}\)
void maintain(int u){//这里我们以维护区间异或和为例
w[u]=xorv[u]=0;//w[u]维护的是有多少插入的元素的结尾在u的子树内
if(ch[u][0]){
w[u]+=w[ch[u][0]];
xorv[u]^=xorv[ch[u][0]]<<1;//类似于x=x*10+ch-'0'
}if(ch[u][1]){
w[u]+=w[ch[u][1]];
xorv[u]^=(xorv[ch[u][1]]<<1)|(w[ch[u][1]]&1);
//记录有多少元素在u节点所代表的这一位上为1
}w[u]=w[u]&1;//我们并不关心子树内有多少元素
}
\((2).\)使用类似线段树的\(\text{Lazy tag}\)去维护整体异或\(1\)之类的操作,但注意在\(\text{pushdown}\)操作时我们如果修改的是\(u\)节点的值,在最开始时就无需对根节点做操作
int tag[N];//以区间异或1为例
void pushdown(int u){
if(tag[u]){
swap(ch[u][0],ch[u][1]);
tag[ch[u][0]]^=1;
tag[ch[u][1]]^=1;
tag[u]=0;
}
}
......
void query(){
pushdown(u);
......
}
......
if(......) tag[rt]^=1;
\(4.k\)进制\(\text{trie}\)
类似于\(\text{0-1trie}\)将整数视为\(k\)进制字符串,具体题目具体分析即可
posted on 2026-07-28 13:39 Underthetwilight 阅读(5) 评论(0) 收藏 举报
浙公网安备 33010602011771号