\(\mathcal{TRIE}\):用于存储和查询字符串的树形结构,相同前缀的字符串共用节点,每个节点存储一个字符。
操作:
-
insert
:单次 \(O(len)\) -
search
:单次 \(O(len)\)
性质 \(1\):若一个字符串 \(T\) 作为前缀,则包含 \(T\) 的所有字符串的“终止节点”一定在以 \(T\) 的“终止节点”为根的子树内。
关于空间:
第一维的上限一般为字符串总长度,也可以是 \(\text{字符种类数}^{\text{最长的字符串长度}}\),在两者中取 \(\min\) 即可。
T1
将 vis
改为计数器,到末尾时还是标记为 \(1\)(因为要判错误),search
中判错误后计数器加一,若为 \(2\) 则 OK
,否则 REAPEAT
。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e6+5,M=31;
int n,m;
int tot,trie[N][M];
int vis[N];
void insert(string s){
int cur=0;
for(int i=0;i<s.size();i++){
if(!trie[cur][s[i]-'a'])
trie[cur][s[i]-'a']=++tot;
cur=trie[cur][s[i]-'a'];
}
vis[cur]=1;
}
string search(string s){
int cur=0;
for(int i=0;i<s.size();i++){
if(!trie[cur][s[i]-'a'])
return "WRONG\n";
cur=trie[cur][s[i]-'a'];
}
if(!vis[cur]) return "WRONG\n";
vis[cur]++;
if(vis[cur]==2) return "OK\n";
return "REPEAT\n";
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
string s; cin>>s,insert(s);
}
cin>>m;
for(int i=1;i<=m;i++){
string s; cin>>s,cout<<search(s);
}
return 0;
}
T2
运用性质 \(1\),我们维护 \(cnt_i\) 表示节点 \(i\) 的被经过次数,对于 \(s\) 建 trie,把 \(t\) 丢到里面匹配,答案即为 \(cnt_{j}\)(\(j\) 即为匹配的最终节点),此时 vis
无用处(\(t\) 不一定在 \(s\) 中,因此\(end\) 不一定有标记)。注意多测清空不能用 memset
。
code
#include<bits/stdc++.h>
using namespace std;
const int N=3e6+5,M=80;
int t,n,q;
int tot,trie[N][M];
//bool vis[N];
int cnt[N];
int g(char c){
return c-'0';
}
void insert(string s){
int cur=0;
for(int i=0;i<s.size();i++){
if(!trie[cur][g(s[i])])
trie[cur][g(s[i])]=++tot;
cur=trie[cur][g(s[i])],cnt[cur]++;
}
//vis[cur]=1;
}
int search(string s){
int cur=0;
for(int i=0;i<s.size();i++){
if(!trie[cur][g(s[i])])
return 0;
cur=trie[cur][g(s[i])];
}
//if(!vis[cur]) return 0;
return cnt[cur];
}
int main(){
cin>>t;
while(t--){
for(int i=0;i<=tot;i++){
cnt[i]=0;
for(int j=0;j<M;j++) trie[i][j]=0;
}
tot=0;
cin>>n>>q;
for(int i=1;i<=n;i++){
string s; cin>>s,insert(s);
}
for(int i=1;i<=q;i++){
string s; cin>>s,cout<<search(s)<<'\n';
}
}
return 0;
}
作业 T1
首先我们对信息建 trie。注意到本题对于每一条暗号,它作为信息的前缀 / 信息作为它的前缀 均可进行匹配。前者运用性质 \(1\) 即可;后者我们维护一个 \(end_i\) 表示节点 \(i\) 作为一个信息的最终节点的次数,insert
中每插入一个单词就加一,search
中令答案不断加上经过节点的 \(end_i\)(求出信息作为它的前缀的个数),若走不下去了就直接返回答案(“这个前缀长度必须等于暗号和那条信息长度的较小者”),否则令答案 \(+ \ cnt_{j} - end_{j}\) 再输出(\(-\ end_{j}\) 是因为前面加过了一遍)。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=55;
int m,n;
int tot,trie[N][M];
int cnt[N],vis[N];
void insert(string s){
int cur=0;
for(int i=0;i<s.size();i++){
if(!trie[cur][s[i]])
trie[cur][s[i]]=++tot;
cur=trie[cur][s[i]],cnt[cur]++;
}
vis[cur]++;
}
int search(string s){
int cur=0,ans=0;
for(int i=0;i<s.size();i++){
if(!trie[cur][s[i]])
return ans;
cur=trie[cur][s[i]],ans+=vis[cur];
}
return ans+cnt[cur]-vis[cur];
}
int main(){
cin>>m>>n;
for(int i=1,b;i<=m;i++){
cin>>b; string s; s.resize(b);
for(int j=0;j<b;j++) cin>>s[j];
insert(s);
}
for(int i=1,c;i<=n;i++){
cin>>c; string s; s.resize(c);
for(int j=0;j<c;j++) cin>>s[j];
cout<<search(s)<<'\n';
}
return 0;
}
作业 T2
至今未卡过(90 pts)。。。
考虑对字典建 trie。把文章扔进去匹配(dfs),匹配上一个单词就更新答案即可。注意搜过了的位置不用再搜。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5,M=1e3+5;
int n,m,ans;
string s;
bool chk[N];
int vis[N];
int tot,trie[M][31];
inline void read(string &s) {
s = "";
char c = getchar();
while (c < 'a' || c > 'z') c = getchar();
while (c >= 'a' && c <= 'z')
s += c, c = getchar();
}
inline void write(int x) {
if (x < 10) return putchar(x + '0'), void();
write(x / 10);
putchar(x % 10 + '0');
}
void ins(string s){
int cur=0;
for(int i=0;i<s.size();i++){
if(!trie[cur][s[i]-'a'])
trie[cur][s[i]-'a']=++tot;
cur=trie[cur][s[i]-'a'];
}
vis[cur]++;
}
void sch(int cur,int len){
if(chk[cur]) return; chk[cur]=1;
ans=max(ans,cur); int x=cur,now=0;
while(x<len){
if(trie[now][s[x]-'a']){
now=trie[now][s[x]-'a'],x++;
if(vis[now]) sch(x,len);
}
else break;
}
}
int main(){
//ios::sync_with_stdio(0);
//cin.tie(nullptr),cout.tie(nullptr);
cin>>n>>m;
for(int i=1;i<=n;i++) read(s),ins(s);
for(int i=1;i<=m;i++){
memset(chk,0,sizeof(chk)),read(s);
ans=0,sch(0,s.size());
write(ans),putchar('\n');
}
return 0;
}