WARNING:以下代碼未經測試,若發現錯誤,歡迎指出qwq~
Trie樹(字典樹)
一種簡單的數據結構,可存儲大量字符串,可在$O(len)$的時間內完成插入,刪除,查找等操作。
下面是一個簡單的例子,對於abc,abd,abcd,bcd這四個字符串建Trie樹,如下圖:
其中,紅色節點為一個字符串的結尾。對於任意節點,從根節點到該節點路徑上字符組成的字符串即為該節點表示的字符串。
基本操作
相關變量
1 int root,cnt,ch[1000010][26],son[1000010]; 2 bool flag[1000010]; 3 void init(){ 4 memset(flag,false,sizeof(flag)); 5 memset(son,0,sizeof(son)); 6 memset(ch,0,sizeof(ch)); 7 root=cnt=1; 8 return; 9 }
root即為根節點,cnt用於動態建樹,ch[i][j]表示i節點的第j個子節點(表示字符char('a'+i))的編號,son[i]表示i節點的子節點數,flag[i]表示i節點是否為某個字符串的末尾。
插入
1 void ins(int rt,int dep){ 2 if(dep==len){ 3 flag[rt]=true; 4 return; 5 } 6 if(!ch[rt][s[dep]-'a']){ 7 ch[rt][s[dep]-'a']=++cnt; 8 son[rt]++; 9 } 10 ins(ch[rt][s[dep]-'a'],dep+1); 11 return; 12 }
刪除
1 bool del(int rt,int dep){ 2 if(dep==len){ 3 if(flag[rt]){ 4 flag[rt]=false; 5 return true; 6 } 7 return false; 8 } 9 if(!ch[rt][s[dep]-'a']) 10 return false; 11 if(del(ch[rt][s[dep]-'a'],dep+1)){ 12 if(!son[ch[rt][s[dep]-'a']]&&!flag[ch[rt][s[dep]-'a']]){ 13 ch[rt][s[dep]-'a']=0; 14 son[rt]--; 15 } 16 return true; 17 } 18 return false; 19 }
查找
1 bool query(int rt,int dep){ 2 if(dep==len) 3 return flag[rt]; 4 if(!ch[rt][s[dep]-'a']) 5 return false; 6 return query(ch[rt][s[dep]-'a'],dep+1); 7 }
以上三個是Trie樹的基本操作,下面來講一下Trie樹的其它運用。
拓展運用
求第k小字符串
存儲以每個節點為根的子樹中的末尾節點個數(size[i])即可。
1 void kth(int rt,int dep,int k){ 2 if(k>size[rt]){ 3 puts("have no kth string"); 4 return; 5 } 6 for(int i=0;i<26;i++) 7 if(k>size[ch[rt][i]]) 8 k-=size[ch[rt][i]]; 9 else if(ch[rt][i]){ 10 putchar('a'+i); 11 kth(ch[rt][i],dep+1,k); 12 } 13 return; 14 }
最長公共前綴
用LCA求兩個字符串對應的末尾節點的最近公共祖先即可,時間復雜度O(log2n)。
1 代碼不貼了,懶~~~
最大異或值
將每個數轉化為二進制,添加前綴0至相同位數,然后從最高位開始插入。查詢時從最高位開始查詢是否有與相應位置異或值為1的節點即可。
1 太水了,也不貼代碼了~~~
可持久化Trie樹
在做題過程中,我們常常會遇到求區間第k大字符串,區間與某數異或最大值之內的問題,我們便可以采用可持久化Trie樹來解決這類問題。
依舊以abc,abd,abcd,bcd這四個字符串為例建可持久化Trie,如下圖:

紅色節點意義同上。
基本操作
相關變量
1 int cnt=0,root[1000010],size[1000010],ch[1000010][26]; 2 bool flag[1000010]; 3 void init(){ 4 memset(flag,false,sizeof(flag)); 5 memset(root,0,sizeof(root)); 6 memset(size,0,sizeof(size)); 7 memset(ch,0,sizeof(ch)); 8 cnt=0; 9 return; 10 }
意義同上。
插入
1 void ins(int &now,int last,int dep){ 2 if(!now) 3 now=++cnt; 4 if(dep==len){ 5 flag[now]=true; 6 size[now]=size[last]+1; 7 return; 8 } 9 int sign=s[dep]-'a'; 10 for(int i=0;i<26;i++) 11 if(i!=sign){ 12 ch[now][i]=ch[last][i]; 13 size[now]+=size[ch[last][i]]; 14 } 15 ins(ch[now][sign],ch[last][sign],dep+1); 16 size[now]+=size[ch[now][sign]]; 17 return; 18 }
區間第k小查詢
1 void kth(int rl,int rr,int dep,int k){ 2 if(k>size[rr]-size[rl]){ 3 puts("have no kth string"); 4 return; 5 } 6 for(int i=0;i<26;i++) 7 if(k>size[ch[rr][i]]-size[ch[rl][i]]) 8 k-=size[ch[rr][i]]-size[ch[rl][i]]; 9 else if(size[ch[rr][i]]-size[ch[rl][i]]>0){ 10 putchar('a'+i); 11 kth(ch[rl][i],ch[rr][i],dep+1,k); 12 return; 13 } 14 return; 15 }
區間最大異或值
1 好吧,還是懶得打~~~
