一、字典树
1.创建字典树
儿子数组 ch[p][j]:从节点p沿着j(例如:a 0 b 1)这条边走到子节点
计数数组 cnt[p]:记录以节点p结尾的单词的插入次数
节点编号 idx:用来给节点编号
char s[N];
int ch[N][26],cnt[N],idx;
void insert(char *s)
{
int p=0;
for(int i=0;s[i];i++)
{
int j=s[i]-'a';//映射
if(!ch[p][j])
ch[p][j]=++idx;
p=ch[p][j];
}
cnt[p]++;//插入次数
}
PS:
1.空的字典树只有一个编号为0的根节点
2.从根开始插入,枚举字符串的每个字符。有儿子,p指针走到儿子,没有儿子,则先创建,再 走
3.单词结束点,记录插入次数
2.查询某个单词是否出现,或者出现的次数
int query(char *s)
{
int p=0;
for(i=0;s[i];i++)
{
int j=s[i]-'a';
if(!ch[p][j])
return 0;
p=ch[p][j];
}
return cnt[p];
}
ps:1.从根开始,先扫描字符串
2.有字母s[i]并能走到词尾,则返回插入次数
3.没有字母s[i],返回0;
3.时间复杂度及作用
时间复杂度: O(n)
作用:快速插入字符串、查询字符串
二、相关例题
P2580 于是他错误的点名开始了
一、题目要求
题目背景
XS中学化学竞赛组教练是一个酷爱炉石的人。
他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛 CON900)。
题目描述
这之后校长任命你为特派探员,每天记录他的点名。校长会提供化学竞赛学生的人数和名单,而你需要告诉校长他有没有点错名。(为什么不直接不让他玩炉石。)
输入格式
第一行一个整数 n,表示班上人数。
接下来 n 行,每行一个字符串表示其名字(互不相同,且只含小写字母,长度不超过 50)。
第 n+2 行一个整数 m,表示教练报的名字个数。
接下来 m 行,每行一个字符串表示教练报的名字(只含小写字母,且长度不超过 50)。
输出格式
对于每个教练报的名字,输出一行。
如果该名字正确且是第一次出现,输出 OK
,如果该名字错误,输出 WRONG
,如果该名字正确但不是第一次出现,输出 REPEAT
。
输入输出样例
输入
5 a b c ad acd 3 a a e
输出
OK REPEAT WRONG
说明/提示
- 对于 40% 的数据,n≤1000,m≤2000。
- 对于 70% 的数据,n≤10^4,m≤2×10^4。
- 对于 100% 的数据,n≤10^4,m≤10^5。
upd 2022.7.30upd 2022.7.30:新增加一组 Hack 数据。
二、思路
1.在插入建树后,询问该单词的时候,按普通方法,最好解决的是名字错误,和名字正确且第一次出现。
2.面对名字正确但不是第一次出现,可以在建树过程中令cnt[p]=1;
3.当每次询问时,就比如样例中,第一个出现的a,输出OK,面对第二次出现的a,则需要在第一次出现过cnt[p]=1,变成cnt[p]=2,便可以进行标记,以此输出REPEAT;
三、代码
1.第一种方法:字典树
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1e5+10;
const int inf=0x3f3f3f3f;
int n,m;
int ch[N*32][26],cnt[N*32],idx;
void insert(string s)//建树
{
int p=0;
for(int i=0;i<s.size();i++)
{
int j=s[i]-'a';
if(!ch[p][j])
ch[p][j]=++idx;
p=ch[p][j];
}
//cnt[p]++; 存储以节点p结尾的单词的插入次数
cnt[p]=1;
}
void query(string s)
{
int p=0;
for(int i=0;i<s.size();i++)
{
int j=s[i]-'a';
if(!ch[p][j])
break;//return 0
p=ch[p][j];
}
//return cnt[p]
if(cnt[p]==1)
{
cout<<"OK"<<endl;
cnt[p]=2;
}
else if(cnt[p]==2)
cout<<"REPEAT"<<endl;
else
cout<<"WRONG"<<endl;
}
void solve()
{
cin>>n;
int i,j;
for(i=1;i<=n;i++)
{
string ss;
cin>>ss;
insert(ss);
}
cin>>m;
for(i=1;i<=m;i++)
{
string s;
cin>>s;
query(s);
}
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}
2. 第二种方法:STL容器(利用map<string,int>s)
先简单介绍下,map<string,int>s
map<类型名,类型名>变量名
第一个类型名:key值,key值只在关键字中出现一次
第二个类型名:value值,是key对应的数值
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=2e5+10;
const int inf=0x3f3f3f3f;
int n,m;
map<string,int>s;
void solve()
{
cin>>n;
int i,j;
for(i=1;i<=n;i++)
{
string x;
cin>>x;
s[x]=1;
}
cin>>m;
for(i=1;i<=m;i++)
{
string y;
cin>>y;
if(s[y]==1)
{
cout<<"OK"<<endl;
s[y]=2;
}
else if(s[y]==2)
cout<<"REPEAT"<<endl;
else
cout<<"WRONG"<<endl;
}
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}
/*
map<类型名,类型名>变量名
第一个类型名:key值,key值只在关键字中出现一次
第二个类型名:value值,是key对应的数值
*/
P3879 [TJOI2010] 阅读理解
一、题目要求
题目描述
英语老师留了 N 篇阅读理解作业,但是每篇英文短文都有很多生词需要查字典,为了节约时间,现在要做个统计,算一算某些生词都在哪几篇短文中出现过。
输入格式
第一行为整数 N ,表示短文篇数,其中每篇短文只含空格和小写字母。
按下来的 N 行,每行描述一篇短文。每行的开头是一个整数 L ,表示这篇短文由 L 个单词组成。接下来是 L 个单词,单词之间用一个空格分隔。
然后为一个整数 M ,表示要做几次询问。后面有 M 行,每行表示一个要统计的生词。
输出格式
对于每个生词输出一行,统计其在哪几篇短文中出现过,并按从小到大输出短文的序号,序号不应有重复,序号之间用一个空格隔开(注意第一个序号的前面和最后一个序号的后面不应有空格)。如果该单词一直没出现过,则输出一个空行。
输入输出样例
输入
3 9 you are a good boy ha ha o yeah 13 o my god you like bleach naruto one piece and so do i 11 but i do not think you will get all the points 5 you i o all naruto
输出
1 2 3 2 3 1 2 3 2
说明/提示
对于 30% 的数据, 1≤M≤10^3 。
对于 100% 的数据,1≤M≤10^4,1≤N≤10^3 。
每篇短文长度(含相邻单词之间的空格)5×10^3 字符,每个单词长度 ≤20 字符。
每个测试点时限 22 秒。
感谢@钟梓俊添加的一组数据。
二、思路
唯一麻烦的一点是记录需要查询的单词出现在哪几篇短文中,即题目要求的从小到大输出其对应的序号,再次需要用bitset容器,若用bool数组,则会MLE
简单介绍下bitset容器的优点
bitset容器其实是个01串,可以被看作一个bool数组,
但比bool数组更优的点:节约空间,节约时间,支持基本的位运算。
在bitset容器中,8位占一个字节,而bool数组4位占一个字节。
n位的bitset在执行一次位运算的复杂度可以被看作n/32
三、代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=5e6+10;
const int inf=0x3f3f3f3f;
int n,m,q;
int ch[N][26],cnt[N],idx;
bitset<1005>b[N];//声明一个1005位的bitset,利用bool会MLE
void insert(string s,int x)
{
int p=0;
for(int i=0; i<s.size(); i++)
{
int j=s[i]-'a';
if(!ch[p][j])
ch[p][j]=++idx;
p=ch[p][j];
}
b[p][x]=1;//记录序号
}
void query(string s)
{
int p=0,f=0;
for(int i=0;i<s.size(); i++)
{
int j=s[i]-'a';
if(!ch[p][j])
{
f=1;
break;
}
p=ch[p][j];
}
if(!f)
{
for(int i=1; i<=n; i++)//输出
{
if(b[p][i]==1)
cout<<i<<' ';
}
}
cout<<endl;
}
void solve()
{
cin>>n;
int i,j;
for(i=1; i<=n; i++)
{
cin>>m;
for(j=1; j<=m; j++)
{
string s;
cin>>s;
insert(s,i);//i代表短文编号
}
}
cin>>q;
while(q--)
{
string s;
cin>>s;
query(s);
}
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}
P1481 魔族密码
一、题目要求
题目背景
风之子刚走进他的考场,就……
花花:当当当当~~偶是魅力女皇——花花!!^^(华丽出场,礼炮,鲜花)
风之子:我呕……(杀死人的眼神)快说题目!否则……-_-###
题目描述
花花:……咦好冷我们现在要解决的是魔族的密码问题(自我陶醉:搞不好魔族里面还会有人用密码给我和菜虫写情书咧,哦活活,当然是给我的比较多拉*^_^*)。
魔族现在使用一种新型的密码系统。每一个密码都是一个给定的仅包含小写字母的英文单词表,每个单词至少包含 1个字母,至多 75个字母。如果在一个由一个词或多个词组成的表中,除了最后一个以外,每个单词都被其后的一个单词所包含,即前一个单词是后一个单词的前缀,则称词表为一个词链。例如下面单词组成了一个词链:
- ii;
- intint;
- integerinteger。
但下面的单词不组成词链:
- integerinteger;
- internintern。
现在你要做的就是在一个给定的单词表中取出一些词,组成最长的词链,就是包含单词数最多的词链。将它的单词数统计出来,就得到密码了。
风之子:密码就是最长词链所包括的单词数阿……
输入格式
这些文件的格式是,第一行为单词表中的单词数 N(1≤N≤2000),下面每一行有一个单词,按字典顺序排列,中间也没有重复的单词。
输出格式
输出共一行,一个整数,表示密码。
输入输出样例
输入
5 i int integer intern internet
输出
4
二、思路
第一种方法:利用string里面的find()函数,从后往前查,看有几个单词是它的子串,最后不要忘了加1
第二种方法:利用字典树加dfs
三、代码
第一种:find()函数
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=2100;
const int inf=0x3f3f3f3f;
int n;
string s[N];
void solve()
{
int i,j;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>s[i];
}
int ans=0;
for(i=1;i<=n;i++)
{
int cnt=0;
for(j=1;j<i;j++)
{
if(s[i].find(s[j])==0)//s[j]要找的元素
{
cout<<s[j]<<' '<<s[i]<<endl;
cnt++;//从后往前找,看s[i]有几个不同的子串s[j];
}
}
cout<<endl;
ans=max(ans,cnt);
}
cout<<ans+1<<endl;
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}
/*
string里面find()函数用法:
find():查找指定字符,从某个位置向后开始查找,若查找不到就返回-1
返回值为目标字符的位置(第一个字符位置为0)
*/
第二种:字典树+dfs
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=2e5+10;
const int inf=0x3f3f3f3f;
int n,m,q,ans=0;
int ch[N][26],cnt[N],idx;
void insert(string s)
{
int p=0;
for(int i=0; i<s.size(); i++)
{
int j=s[i]-'a'+1;//a 1
if(!ch[p][j])
ch[p][j]=++idx;
p=ch[p][j];
}
cnt[p]++; //记录以节点p结尾的单词的插入次数
}
void dfs(int x,int step)
{
step+=cnt[x];
bool f=0;
for(int i=1;i<=26;i++)
{
if(ch[x][i])//如果这个字符存在
{
dfs(ch[x][i],step);//接着走
f=1;
}
}
if(f==0)
{
ans=max(ans,step);
}
}
void solve()
{
cin>>n;
int i,j;
for(i=1; i<=n; i++)
{
string s;
cin>>s;
insert(s);
}
dfs(0,0);
cout<<ans<<endl;
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}
P4551 最长异或路径
一、题目要求
题目描述
给定一棵 n 个点的带权树,结点下标从 11 开始到 n。寻找树中找两个结点,求最长的异或路径。
异或路径指的是指两个结点之间唯一路径上的所有边权的异或。
输入格式
第一行一个整数 n,表示点数。
接下来 n−1 行,给出 u,v,w ,分别表示树上的 u 点和 v 点有连边,边的权值是 w。
输出格式
一行,一个整数表示答案。
输入输出样例
输入
4 1 2 3 2 3 4 2 4 6
输出
7
说明/提示
最长异或序列是 1,2,3,答案是 7=3⊕4。
数据范围
1≤n≤100000;0<u,v≤n;0≤w<2^31。
二、思路
1.创建邻接链表
2.计算每个点到root(根)异或值(用dfs)
3.建立01字典树
4.查询
三、代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1e6+10;
const int inf=0x3f3f3f3f;
int n;
int he[N],tot;
int to[N*2],nex[N*2],w[N*2];
int dis[N];
int ch[N*32][2],idx;
void add(int u,int v,int z)//创建邻接表+加边
{
to[tot]=v;
w[tot]=z;
nex[tot]=he[u];
he[u]=tot++;
}
void dfs(int u,int father,int sum)
{
dis[u]=sum;
for(int i=he[u];~i;i=nex[i])
{
int j=to[i],ww=w[i];//ps:to[i]!!!
if(j!=father)//如果这条边的初边不是往上走的
{
dfs(j,u,sum^ww);
}
}
}
void insert(int x)
{
int p=0;
for(int i=31;i>=0;i--)
{
int j=x>>i&1;
if(!ch[p][j])
ch[p][j]=++idx;//不变
p=ch[p][j];
}
}
int query(int x)
{
int p=0,res=0;
for(int i=31;i>=0;i--)
{
int j=x>>i&1;
if(ch[p][!j])
{
res+=1<<i;
p=ch[p][!j];
}
else
p=ch[p][j];
}
return res;
}
void solve()
{
cin>>n;
int i,j;
memset(he,-1,sizeof(he));
for(int i=0;i<n-1;i++)//无向图
{
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
dfs(0,-1,0);//挑0为根节点
/*for(i=0;i<n;i++)
{
cout<<dis[i]<<' ';
}
cout<<endl;*/
for(i=0;i<n;i++)
{
insert(dis[i]);
}
int ans=0;
for(i=0;i<n;i++)
{
ans=max(ans,query(dis[i]));
}
cout<<ans<<endl;
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}