835. Trie字串統計
模板題:
維護一個字串集合,支援兩種操作:
I x
向集合中插入一個字串 x;Q x
詢問一個字串在集合中出現了多少次。
共有 N 個操作,所有輸入的字串總長度不超過 10^5,字串僅包含小寫英文字母。
輸入格式
第一行包含整數 N,表示運算元。
接下來 N 行,每行包含一個操作指令,指令為 I x
或 Q 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 四谷夕雨
解疑:
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';
}
- 這題是跟上述不同的:這個是實現字首和的異或相加,在進行異或對操作;