Trie——解決字串搜尋、異或最值問題
- 在說到Trie之前,我們設想如下問題:
給我們1e5個由小寫字母構成的不重複的字串,每個字串長度不超過6,之後是1e5次查詢操作,每次給我們一個字串,要求我們判斷這個字串是否出現過,如果是則求出它是多少個其他的字串的字首,並在之後的操作中無視這個字串(刪除)。
- 查詢是否出現這個可以用set或者hash,但是字首,,其實也有辦法,但是這裡要介紹的方法是使用一種易於理解的資料結構——Trie
建立Trie
- 字典樹Trie的結構比較自然,如對於字串集合{"abca", "ab", "bcd", "abcde", "bcde", "bcdf"},可以建立一棵這樣的Trie:
可知:
- 每一條邊代表一個字元
- 節點不為0代表從根節點到此為一個完整的字串
實現的方法也比較簡單,建立一棵單向樹,每個節點都有
-
26個子節點(所有小寫字母);
-
一個isstr,bool值,代表這裡是否為一個字串的結束
-
一個vis,int值,代表到這裡已經有多少個字串遍歷過(是多少字串的字首)
一開始樹為空,我們每得到一個字串,就從它的第一個字元開始,從根節點遍歷,沒有對應的節點就建立,同時把所經過的節點的vis值加一,到最後字串終止時,在終止的節點處置isstr為true。
更多操作
1.查詢
從樹的形狀就可以看出,這是一棵專門查詢字串存在與否的資料結構(同時也付出了巨大的空間代價)。查詢操作很簡單,從根節點開始,按照要查詢的字串的每一位來遍歷,如果遇到空節點或者終止時的節點的isstr為false,則字串不存在,否則存在。
2.查詢某個串是多少個字串的字首
這個就是讀取要查詢的字串的終止節點的vis值即可
3.刪除某個字串
首先查詢成功之後,我們從底部開始回溯刪除這個串的資訊,將終止節點的isstr置為false,同時將路過的vis值減一,如果vis值減為0則將將此節點在其父節點中刪除即可。
01Trie解決xor最值問題
題意
給我們一個序列A和序列B,要求我們找到B序列的一種排列,使得
中的序列C字典序最小,並輸出序列C,長度<=3e5,每個數小於2^30
思路
-
首先按照運算規則C xor B == A意味著A xor B == C 也就是說 尋找一種B的排列,使得A逐個與B異或的結果的字典序最小
-
3e5基本不可能在其他操作中間搞什麼排序了,應該從異或運算的結果出發,我們追求結果的字典序最小,也就是說,對於每個Ai,我們都要找到一個Bj,使得其異或結果最小。這個如果直接找的話,是比較難的。但是如果使用Trie的話,可以直接求得每一個Ai的最小結果,方法如下:
-
對B序列建立一棵只包含01字元的Trie(也就是二叉樹),我們規定A與B的每一個數都是30位二進位制數,左邊補零,從左邊的第一位開始建樹。
-
B序列的01Trie建好之後,對A序列從1到n每個數都按位在Trie中遍歷,一開始先設ans為0,之後優先走與當前自己的二進位制位相同的邊,如果沒有則讓ans或上這一位的1。(確保ans儘可能地小)
-
之後輸出這個ans,並將走過的路線所代表的Bi刪除掉就可以
-
-
可以看出時間複雜度為
\[O(30*n) \] -
對空間複雜度來說,不要使用滿二叉樹的儲存方式,使用動態開點的方式,空間複雜度不超過
\[O(30*n) \]
01Trie程式碼如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n;
int aa[10000005][2] = {{0}}, vv[10000005][2] = {{0}}, co = 1;
int A[300005] = {0};
void add(int x)
{
int o = 1;
for (int i = 29; i >= 0; --i)
{
int bitt = (x >> i) & 1;
if (!aa[o][bitt])
{
aa[o][bitt] = ++co;
}
++vv[o][bitt];
o = aa[o][bitt];
}
}
int trie(int x)
{
int o = 1, ans = 0;
for (int i = 29; i >= 0; --i)
{
int bitt = (x >> i) & 1;
if (vv[o][bitt])
{
--vv[o][bitt];
o = aa[o][bitt];
}
else
{
--vv[o][bitt ^ 1];
o = aa[o][bitt ^ 1];
ans |= (1 << i);
}
}
return ans;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &A[i]);
}
for (int i = 1; i <= n; ++i)
{
int xx;
scanf("%d", &xx);
add(xx);
}
for (int i = 1; i < n; ++i)
{
printf("%d ", trie(A[i]));
}
printf("%d", trie(A[n]));
return 0;
}