洛谷 P2580 於是他錯誤的點名開始了 hash/trie樹
題目背景
XS中學化學競賽組教練是一個酷愛爐石的人。
他會一邊搓爐石一邊點名以至於有一天他連續點到了某個同學兩次,然後正好被路過的校長發現了然後就是一頓歐拉歐拉歐拉(詳情請見已結束比賽CON900)。
題目描述
這之後校長任命你為特派探員,每天記錄他的點名。校長會提供化學競賽學生的人數和名單,而你需要告訴校長他有沒有點錯名。(為什麽不直接不讓他玩爐石。)
輸入輸出格式
輸入格式:
第一行一個整數 n,表示班上人數。接下來 n 行,每行一個字符串表示其名字(互不相同,且只含小寫字母,長度不超過 50)。第 n+2 行一個整數 m,表示教練報的名字。接下來 m 行,每行一個字符串表示教練報的名字(只含小寫字母,且長度不超過 50)。
輸出格式:
對於每個教練報的名字,輸出一行。如果該名字正確且是第一次出現,輸出“OK”,如果該名字錯誤,輸出“WRONG”,如果該名字正確但不是第一次出現,輸出“REPEAT”。(均不加引號)
輸入輸出樣例
輸入樣例#1: 復制5 a b c ad acd 3 a a e輸出樣例#1: 復制
OK REPEAT WRONG
說明
對於 40%的數據,n≤1000,m≤2000;
對於 70%的數據,n≤10000,m≤20000;
對於 100%的數據, n≤10000,m≤100000。
第一眼就覺得是hash,寫完一丟上去,RE??,開大數組減小base再來一遍,RE??,再來一遍.......終於,在第七遍RE後,我發現事情並不簡單,下了個數據看看,明明自己機子上都能過,wtf??,被逼無奈下掏出trie樹
先介紹一手trie樹
trie樹,中文名字典樹(沒錯用英文只是為了裝逼),先看張圖
(以上來自百度)
除了根節點外(為什麽等下會說),字典樹中的每一個節點都代表一個字母,對任意一顆子樹進行遍歷都可以得到一個串,如上圖,從最左邊一路遍歷下去,依次經過a,d,d或a,m;可以得到add這個串或者am這個串,那如果我有一個ad的串該怎麽辦呢,這裏引入一個標記,例如有一個串ad,在構建字典樹時,我就對d這個節點打一個標記,表示從開頭遍歷到當前節點是一個串
對於一個trie[i][j]表示從i出發到i的第j棵子樹,那如何區分呢,我們規定一下兩個值
第一個,是進入trie的順序,此時相同字符的值可能不同
第二個,是節點子樹的值,此時相同字符的值一定相同(看不懂的話下面會結合代碼講)
介紹下trie的兩個基本操作:add和find
add
char ch[MAXN];//ch為待處理數組
void add() { int len=strlen(ch),rt=0;//獲取長度,根節點為0 for(int i=0;i<len;i++) { int k=ch[i]-‘a‘;//第二個標記,k代表的是節點子樹對應的值 if(!trie[rt].son[k])//如果沒有這個節點 trie[rt].son[k]=++tot;//第一個標記,tot的值即為進入trie的順序 rt=trie[rt].son[k];//向下走 } trie[rt].have=1;//結束時打上一個標記,表示到目前是一個串 }
find
int find() { int len=strlen(ch),rt=0; for(int i=0;i<len;i++) { int k=ch[i]-‘a‘; //前面的基本一樣 if(!trie[rt].son[k])return 3;//如果走不下去了,說明沒有這個串,返回3,表示沒有 rt=trie[rt].son[k]; //向下走 } if(!trie[rt].have)return 3;//如果沒有標記,說明沒有這個串,返回3,表示沒有 if(!trie[rt].cnt)//如果第一次訪問 { trie[rt].cnt++; return 1;//返回1,表示第一次出現 } return 2;否則返回2,表示出現過了 }
完整代碼
#include<bits/stdc++.h> using namespace std; const int MAXN=10+1e6; struct node{ int son[27]; bool have; int cnt; }trie[MAXN]; int n,tot; char ch[MAXN]; void add() { int len=strlen(ch),rt=0; for(int i=0;i<len;i++) { int k=ch[i]-‘a‘; if(!trie[rt].son[k]) trie[rt].son[k]=++tot; rt=trie[rt].son[k]; } trie[rt].have=1; } int find() { int len=strlen(ch),rt=0; for(int i=0;i<len;i++) { int k=ch[i]-‘a‘; if(!trie[rt].son[k])return 3; rt=trie[rt].son[k]; } if(!trie[rt].have)return 3; if(!trie[rt].cnt) { trie[rt].cnt++; return 1; } return 2; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",ch); add(); } scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",ch); int k=find(); if(k==1) cout<<"OK"<<endl; if(k==2) cout<<"REPEAT"<<endl; if(k==3) cout<<"WRONG"<<endl; } return 0; }
附贈蜜汁RE的hash
#include<bits/stdc++.h> using namespace std; const int MAXN=105; struct node{ int son[MAXN]; bool have; int cnt; }trie[MAXN]; int n,tot; char ch[MAXN]; void add() { int len=strlen(ch),rt=0; for(int i=0;i<len;i++) { int k=ch[i]-‘a‘; if(!trie[rt].son[k]) trie[rt].son[k]=++tot; rt=trie[rt].son[k]; } trie[rt].have=1; } int find() { int len=strlen(ch),rt=0; for(int i=0;i<len;i++) { int k=ch[i]-‘a‘; if(!trie[rt].son[k])return 3; rt=trie[rt].son[k]; } if(!trie[rt].have)return 3; if(!trie[rt].cnt) { trie[rt].cnt++; return 1; } return 2; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",ch); add(); } scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",ch); int k=find(); if(k==1) cout<<"OK"<<endl; if(k==2) cout<<"REPEAT"<<endl; if(k==3) cout<<"WRONG"<<endl; } return 0; }
參考大佬@ Niko的題解
洛谷 P2580 於是他錯誤的點名開始了 hash/trie樹