underthetwilight

trie树

\(1.\text{trie}\)树的结构和性质

\(\text{trie}\)树,即字典树,其结构特点在于每一条边代表一个字符,一条从根出发的路径代表一个前缀或字符串,利用这个特点,在\(\text{trie}\)上我们可以快速的查找前缀img
例如,在上述的树中,\(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)    收藏  举报

导航