Trie

小码king發表於2024-10-06

835. Trie字串統計

模板題:

維護一個字串集合,支援兩種操作:

  1. I x 向集合中插入一個字串 x;
  2. Q x 詢問一個字串在集合中出現了多少次。

共有 N 個操作,所有輸入的字串總長度不超過 10^5,字串僅包含小寫英文字母。

輸入格式

第一行包含整數 N,表示運算元。

接下來 N 行,每行包含一個操作指令,指令為 I xQ x 中的一種。

輸出格式

對於每個詢問指令 Q x,都要輸出一個整數作為結果,表示 x 在集合中出現的次數。

每個結果佔一行。

資料範圍

1≤N≤2∗10^4

輸入樣例:

5
I abc
Q abc
Q ab
I ab
Q ab

輸出樣例:

1
0
1

個人理解

個人感覺這個Trie是比較簡單理解的,直接給上注意點還有圖解;

AC程式碼:

#include <iostream>
using namespace std;
//定義:不超過 10^5;
//符合要求即可;
const int Ma=100010;
//這裡的26是a~z(字串僅包含小寫英文字母)
//con[Ma]是記錄Trie每個父節點最後一個的子節點(有圖解);
//idx是記錄c的“下標”;
int Trie[Ma][26],con[Ma],idx;   //這裡容易誤解的點:26 a~z並不是層級;而是以a~z帶頭的父節點;
char str[Ma];
//這是構造Trie樹;
void insert(char str[]){
    int p=0;
    for(int i=0;str[i];i++){
        int c=str[i]-'a';
        //如果沒有記錄這個字元,idx開闢一個位置來兜住;
        if(!Trie[p][c]){
            Trie[p][c]=++idx;
        }
        //p來指向下一個;
        p=Trie[p][c];
    }
    //記錄最後一個字元的位置的個數; 比如 abcd 記錄一次,abcd 再來一次 con[p]=2;
    con[p]++;
}
//查詢
int search(char str[]){
    int p=0;
    for(int i=0;str[i];i++){
        int c=str[i]-'a';
        //按順序找下去,不對直接返回0;
        if(!Trie[p][c]){
            return 0;
        }else{
            //指向下一個;
            p=Trie[p][c];
        }
    }
    //返回結果;
    return con[p];
}
int main(){
    int n;
    cin >> n;
    while(n--){
        char c;
        cin >> c >> str;
        if(c=='I'){
           insert(str);
        }else{
            cout << search(str) << endl;
        }
    }
    return 0;
}

圖解:來源--------AC 四谷夕雨

Trie2.PNG

解疑:

Trie[p][c]=++idx; //這是什麼?idx幹什麼用的?    
  • 一維下標:父節點的位置(層級)
    二維下標:當前節點的位置(az->025)
    值:當前節點的id(用idx標識,唯一性)
  • 這裡是可以自己圖畫手推的:手推也可能就會發現用這種方法的話,是會浪費一層裡的空間的.......這就自定義想了,在下面我會給出一些例題,是很明顯的吧,有最佳化可以q我
  • 自己手推很重要,本人在沒手推之前,總是覺得字元之間會重複什麼的.......,手推才意識到idx的強大,idx是即將待操作的結點下標 (看自己的理解吧);
  • 哦對,在這以前要知道怎麼構建Trie,要不然夠嗆,比如abcd 的“路徑”是可以儲存 abc的只需記錄最後一個節點;

相關例題:

AC. 最大異或對:

在給定的 N 個整數 A1,A2……AN中選出兩個進行 xor(異或)運算,得到的結果最大是多少?

輸入格式

第一行輸入一個整數 N。

第二行輸入 N 個整數 A1~AN。

輸出格式

輸出一個整數表示答案。

資料範圍

1≤N≤10^5
0≤Ai<2^31

輸入樣例:

3
1 2 3

輸出樣例:

3

解析:

  • 異或對:隨便兩個數進行邏輯異或操作求出兩兩匹配之間最大的異或值;

  • 邏輯異或操作: 異或(XOR)

    邏輯異或運算,運算規則:相異為一,相同為零。即兩個運算元不一樣時結果為1,兩個運算元相同時結果為0。(這都是二進位制的形式比較)

    運算元1 運算元2 結果值
    1 1 0
    1 0 1
    0 1 1
    0 0 0

AC程式碼(暴力):

#include<iostream>
using namespace std;
int sum[100005],a[100005];
int main(){
    int n,ans=0;
    cin >> n;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        //字首和;
        sum[i]=sum[i-1]^a[i];
    }
    //兩兩比較;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            ans=max(ans,sum[i]^sum[j-1]);
        }
    }
    cout << ans << endl;
    return 0;
}
  • 暴力的做法是:第一個迴圈時間複雜度O(n),第二個O(N^2),時間複雜度肯定是爆掉的;在下面用Trie最佳化一下

AC程式碼(Trie):

#include <iostream>
using namespace std;
//0≤Ai<2^31所以最高位是第30位。
//因為是正數,所以要保證第一位是1
const int Ma=3100010,N=1e5+10;
#define int long long
int e[Ma][2],idx;
int a[N];
//構建Trie;
void insert(int x){
    int p=0;
    for(int i=30;i>=0;i--){
        int u=x >> i & 1;
        if(!e[p][u]) e[p][u]=++idx;
        p=e[p][u];
    }
}
int search(int x){
    int p=0,res=0;
    for(int i=30;i>=0;i--){
        //捋明白位移情況在配合上述的Trie手推就很好理解了
        int u=x >> i & 1;
        //判斷同層有沒有相反的數,只有1或者0,1取反找0,0取反找1;沒有走原路;(建議畫圖理解)
        if(e[p][!u]){
            p=e[p][!u];
            res=res*2+1;  //這裡跟轉10進位制一樣的0*10+n
        }
        else{
            //走原路,不要把取反的值帶過來看了......
            p=e[p][u];
            res=res*2+0;
        }
    }
    return res;
}
signed main(){
    int n;
    cin >> n;
    if(n==1){int x;cin >> x;cout << x << endl;return 0;}
    for(int i=0;i<n;i++){
        cin >> a[i];
        insert(a[i]);
    }
    int res=0;
    for(int i=0;i<n;i++){
        res=max(res,search(a[i]));
    }
    cout << res << '\n';
}

總結:手推, 手推 , 還是手推;

再分享一道馬蹄的題:

MT2055最大異或和:

給定一串手鍊,每個珠子被賦予一個價值wi,現要從中擷取連續的一段,使得他們的異或和最大(注意,手鍊還未串上)。

格式

輸入格式:

第11行包含一個正整數N;
第22行n個正整數wi​,表示珠子價格。

輸出格式:

一個正整數,輸出最大異或和。

樣例 1

輸入:

5
1 2 3 4 5

輸出:

7
備註

其中:n≤2000,wi≤100000

早期程式碼: 字首和+暴力

#include<iostream>
using namespace std;
int sum[100005],a[100005];
int main(){
    int n,ans=0;
    cin >> n;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        //字首和;
        sum[i]=sum[i-1]^a[i];
    }
    //第一個和每一個進行比較;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            ans=max(ans,sum[i]^sum[j-1]);
        }
    }
    cout << ans << endl;
    return 0;
}

後期的最佳化: 字首和+Trie

#include <iostream>
using namespace std;
const int Ma=3100010,N=1e5+10;
#define int long long
int e[Ma][2],idx;
int a[N];
void insert(int x){
    int p=0;
    for(int i=30;i>=0;i--){
        int u=x >> i & 1;
        if(!e[p][u]) e[p][u]=++idx;
        p=e[p][u];
    }
}
int search(int x){
    int p=0,res=0;
    for(int i=30;i>=0;i--){
        int u=x >> i & 1;
        if(e[p][!u]){
            p=e[p][!u];
            res=res*2+1;
        }
        else{
            p=e[p][u];
            res=res*2+0;
        }
    }
    return res;
}
signed main(){
    int n;
    cin >> n;
    if(n==1){int x;cin >> x;cout << x << endl;return 0;}
    for(int i=0;i<n;i++){
        cin >> a[i];
        //字首和
        a[i]^=a[i-1];
        insert(a[i]);
    }
    int res=0;
    for(int i=0;i<n;i++){
        res=max(res,search(a[i]));
    }
    cout << res << '\n';
}
  • 這題是跟上述不同的:這個是實現字首和的異或相加,在進行異或對操作;

相關文章