目錄異或對
可持久化 trie 樹
總結trie 樹(字典樹)的名字告訴了我們一切,就是一顆像字典一樣的樹。
先不管怎麼實現,先讓我們了解它有什麼用。
實現字串快速檢索(「檢」即看乙個字串有沒有出現過,「索」即檢視字串相關資訊)
實現最大異或對相關問題(基於貪心思想,之後會講)
當然,具體用處要看具體問題與具體情況,這裡僅僅給出常用的兩種方式。
接下來我們將一起探索 trie 樹的實現。
trie 樹的本質是一顆多叉樹,它的基礎操作包括:初始化、插入與檢索。
插入與檢索的時間複雜度都為 \(o(c)\) ( \(c\) 為該字串的長度),但是空間複雜度高達 \(o(nc)\) (\(n\) 為節點的個數)。
可見 trie 樹對空間要求較高,這就要求同學們對於空間大小有準確的判斷。
其實沒什麼好說的,沒有特殊的初始化(都為 0 就可以了)。
唯一需要注意的是 \(tot=1\) ,\(tot\) 是什麼之後再說,記住就可以了。
當要插入乙個字串 \(s\) 是,令乙個指標 \(p=1\) (即指向根節點),然後依次掃瞄 \(s\) 中的每乙個字元 \(c\):
若 \(p\) 的 \(c\) 字元指標指向乙個已經存在的節點 \(q\) ,則令 \(p=q\)。
若 \(p\) 的 \(c\) 字元指標指向空,則新建乙個節點 \(q\) ,令 \(p\) 的 \(c\) 字元指標指向 \(q\) ,然後 \(p=q\)。
當 \(s\) 的字元掃瞄完畢時,在當前節點 \(p\) 上標記它是乙個字串的末尾。
注意: 在《演算法競賽高階指南》中,用 \(end\) 陣列標記字串末尾,但 \(end\) 是 \(c++11\)的關鍵字,所以容易發生編譯錯誤(可能本地編譯沒有問題,但提交就是不是到**錯了),所以應換成其他名字。
**如下:(這裡沒有改動 \(end \) 陣列,但考試時一定要改動)
void insert(string s)
for(int i=1;i<=m;i++)
return 0;
}
字首統計
trie 的簡單運用僅僅有了一點點改變。
同樣的把這 \(n\) 個字串插入到 \(trie\) 中,在每個節點中記錄乙個 \(cnt\) 表示該節點是多少個字串的末尾節點。
**如下:
#include#include#include#include#include#define n 1000010
using namespace std;
int n,m,cnt[n],trie[n][30],tot=1;
char a[n];
void insert(char* a)-1\),所以是 \(32\) 位)
接著列舉每乙個數,與在 \(trie\) 中檢索相類似的取查詢,每次盡量找 \(xor a[i]\) 的一位就好(貪心)
這個貪心的正確性是很容易證明的,舉個栗子:
\(a=「00000000」\)
\(b=「10000000」\),\(axorb=「10000000」\)
\(c=「01111111」\),\(axorc=「01111111」\)
那麼按照貪心策略,\(a\) 的最大異或對為 \(axorb\) (即使 \(b\) 只有第一位與 \(a\) 不同,\(axorb\) 還是優於 \(axorc\))
其實很好實現的,就只要按照上面說的做就好了:
#include#include#include#include#include#define n 100010
using namespace std;
int n,a[n],trie[n*32][5],tot=1;//注意trie大小的定義
void insert(int x)
return;
}int search(int x)edge[2*n];
void addedge(int x,int y,int z)
void dfs(int x,int fa)
return;
}void insert(int x)
return;
}int search(int x)\bigoplus ...\bigoplus a_n=a_\bigoplus \bigoplus^_ a_i
\]所以:\(a[p] \bigoplus a[p+1] \bigoplus ... \bigoplus a[n] \bigoplus x=s[p−1] \bigoplus s[n] \bigoplus x\)。(其中 \(s[i]=\bigoplus_^n a_i\))
此時查詢轉變為:已知 \(val=\mathrm\),求乙個 \(p\in[l-1,r-1]\),使得 \(\mathrm\)最大。
可以構建一顆可持久化 \(\mathrm\),第 \(i\) 個版本為插入了 \(s[i]\) 後的 \(\mathrm\) 樹。
每次查詢,從根節點開始,貪心地選與這一位相反的值。(貪心證明參見上文)
此外,還有乙個 \(l-1\leq p\leq r-1\) 的限制。
先考慮 \(p\leq r-1\),查詢第 \(r-1\) 個版本的 \(\mathrm\) 即可,因為此時不可能訪問到 \(r-1\) 之後的 \(s\)。
再考慮 \(l-1\leq p\),對每個節點維護乙個 \(latest\)值,表示子樹中所有 \(s\) 值的下標的最大值。
這樣,在查詢時只訪問 \(latest\geq l-1\) 的節點就行了。
上**:
#include#include#include#include#include#define n 600010
using namespace std;
int n,m,s[n],root[n],tot=0;
int trie[n*24][2],latest[n*24];
int read()
void insert(int i,int k,int p,int q)
int c=s[i]>>k & 1;
if(p) trie[q][c^1]=trie[p][c^1];
trie[q][c]=++tot;
insert(i,k-1,trie[p][c],trie[q][c]);
latest[q]=max(latest[trie[q][0]],latest[trie[q][1]]);
return;
}int ask(int p,int val,int k,int limit)
int main()
char str[2];
for(int i=1;i<=m;i++)
else
} return 0;
}
在解決字串相關問題以及異或對相關問題是可以往 trie 樹一方面想一想。
當然只有這麼多的做題量是遠遠不夠的,請讀者務必自己尋找更多相關習題。
注:全篇大多思想均來自:《演算法競賽高階指南》。
可持久化 \(trie\) 例題部分 部分講解來自 這篇部落格
完結撒花。
Trie樹(字典樹)
trie樹的核心思想是用空間換時間,通過在樹中儲存字串的公共字首,來達到加速檢索的目的。例如,對於一棵儲存由英文本母組成的字串的trie樹,如下圖 trie樹在實現的時候,可以用左兒子右兄弟的表示方法,也可以在每個節點處開設乙個陣列,如上圖的方法。trie樹的主要操作是插入 查詢,也可以進行刪除。插...
字典樹 Trie樹
字典樹 trie樹 顧名思義是一種樹形結構,屬於雜湊樹的一種。應用於統計 排序 查詢單詞 統計單詞出現的頻率等。它的優點是 利用字串的公共字首來節約儲存空間,最大限度地減少無謂的字串比較,查詢效率比雜湊表高。字典樹的結構特點 根節點不代表任何字元。其他節點從當前節點回溯到根節點可以得到它代表的字串。...
字典樹 trie樹
amy 56 ann 15 emma 30 rob 27 roger 52首先存入amy,level 0表示根,不持有資料。其餘每個節點持有乙個字元 葉子節點持有資料,且持有的字元為 0 level 0 root a level 1 m level 2 y level 3 0 56 level 4新...