字典树
字典树是一种有关字符串的数据结构:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较
首先我用的是字典树的数组写法(还有指针写法直观不过容错率高):trie[maxn][26]
前面的maxn表示最大节点数26是含有字母的写法,这个数是可以随题意改变的,二进制就是2。trie[i][j]里存的是“编号”,又或者你可以把它看做一个“指针”(和数组类似:数组下表其实是个指针)。第一个i为节点,每个节点号一定不同,第二个j是下一个节点的名字,可以相同(i为身份证号,j为名字),其含义是:节点号为i的下一个叫j的节点下标是多少(也就是:身份证为i的那个人的(名字叫)j儿子的身份证是多少)。
首先介绍几个函数:
insert
void insert(char s[]){
int root = 0;
for(int i = 0;s[i];i++){
int id = s[i] - 'a';
if(!trie[root][id])trie[root][id] = ++tot;
root = trie[root][id];
}
end[root] = true;
}
很容易理解:如果没有那个节点(字母)就新增节点,同时给予其编号,如果有节点,就迭代继续下一个字母。最后在单词的末尾打一个标记就好了,表示这个字母有单词结束。
可以引进其他的数组来存一些其他的东西:如可以用数组tim[ ]表示某个字母被“路过”过几次;end[ ]数组也可以不用bool型,改用int型可以表示有几个单词在这个字母处结束等等......
find
int find(char s[]){
int root = 0;;
for(int i = 0;s[i];i++){
int id = s[i] - 'a';
if(!trie[root][id])return 0;
root = trie[root][id];
}
if(!end[root])return 0;
else{
if(vis[root])return 2;
else{
vis[root] = true;
return 1;
}
}
}//查到单词返回1;第二次查到该单词返回2;没有该单词返回0;
和insert函数非常相似,相信不难看懂,这里就不加解释了
注意
因为是数组代替指针的写法,应注意maxn开的大小:
具体题目具体分析:maxn最多是节点的个数,此题中有26个字母,考虑最不优,maxn应为26 * 最长字符串长度
最后需要指出的是:字典树那个数组并不代表节点,而是代表边,字母存在的位置是边,我们只是把第一个点存为NULL,实现了以点带边,画个图应该就很好理解了
P2580 于是他错误的点名开始了
题目背景
XS中学化学竞赛组教练是一个酷爱炉石的人。
他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛CON900)。
题目描述
这之后校长任命你为特派探员,每天记录他的点名。校长会提供化学竞赛学生的人数和名单,而你需要告诉校长他有没有点错名。(为什么不直接不让他玩炉石。)
输入输出格式
输入格式:
第一行一个整数 n,表示班上人数。接下来 n 行,每行一个字符串表示其名字(互不相同,且只含小写字母,长度不超过 50)。第 n+2 行一个整数 m,表示教练报的名字。接下来 m 行,每行一个字符串表示教练报的名字(只含小写字母,且长度不超过 50)。
输出格式:
对于每个教练报的名字,输出一行。如果该名字正确且是第一次出现,输出“OK”,如果该名字错误,输出“WRONG”,如果该名字正确但不是第一次出现,输出“REPEAT”。(均不加引号)
字典树的板题,两个函数(参见上面)写对就好
注意事项在代码中体现:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
int RD(){
int flag = 1,out = 0;char c = getchar();
while(c < '0' || c > '9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const int maxn = 1010000;
int num,m,tot;
int trie[maxn][26];//字典树
bool vis[maxn];//是否点过名
bool end[maxn];//单词在这个节点结束
char c[66];
void insert(char s[]){
int root = 0;
for(int i = 0;s[i];i++){
int id = s[i] - 'a';
if(!trie[root][id])trie[root][id] = ++tot;
root = trie[root][id];
}
end[root] = true;
}
int find(char s[]){
int root = 0;;
for(int i = 0;s[i];i++){
int id = s[i] - 'a';
if(!trie[root][id])return 0;
root = trie[root][id];
}
if(!end[root])return 0;
else{
if(vis[root])return 2;
else{
vis[root] = true;
return 1;
}
}
}
int main(){
num = RD();
for(int i = 1;i <= num;i++){
cin>>c;
insert(c);
}
m = RD();
for(int i = 1;i <= m;i++){
cin>>c;
int flag = find(c);//这里先计算出flag,不能在if中直接写:直接写会导致函数运行次数不为1次,导致误判为"REPEAT"
if(flag == 1)cout<<"OK"<<endl;
else if(flag == 0)cout<<"WRONG"<<endl;
else cout<<"REPEAT"<<endl;
}
return 0;
}
P2922 [USACO08DEC]秘密消息Secret Message
题目描述
Bessie is leading the cows in an attempt to escape! To do this, the cows are sending secret binary messages to each other.
Ever the clever counterspy, Farmer John has intercepted the first b_i (1 <= b_i <= 10,000) bits of each of M (1 <= M <= 50,000) of these secret binary messages.
He has compiled a list of N (1 <= N <= 50,000) partial codewords that he thinks the cows are using. Sadly, he only knows the first c_j (1 <= c_j <= 10,000) bits of codeword j.
For each codeword j, he wants to know how many of the intercepted messages match that codeword (i.e., for codeword j, how many times does a message and the codeword have the same initial bits). Your job is to compute this number.
The total number of bits in the input (i.e., the sum of the b_i and the c_j) will not exceed 500,000.
Memory Limit: 32MB
POINTS: 270
贝茜正在领导奶牛们逃跑.为了联络,奶牛们互相发送秘密信息.
信息是二进制的,共有M(1≤M≤50000)条.反间谍能力很强的约翰已经部分拦截了这些信息,知道了第i条二进制信息的前bi(l《bi≤10000)位.他同时知道,奶牛使用N(1≤N≤50000)条密码.但是,他仅仅了解第J条密码的前cj(1≤cj≤10000)位.
对于每条密码J,他想知道有多少截得的信息能够和它匹配.也就是说,有多少信息和这条密码有着相同的前缀.当然,这个前缀长度必须等于密码和那条信息长度的较小者.
在输入文件中,位的总数(即∑Bi+∑Ci)不会超过500000.
也是字典树非常基础的运用了:不同的是,这里的end[ ]数组就是int型的,表示有几个单词在这里结束,最后理解好题意就行,难度不大
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
int RD(){
int flag = 1,out = 0;char c = getchar();
while(c < '0' || c > '9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const int maxn = 500000;
int n,m,tot;
int trie[maxn][2];
int end[maxn];
int vis[maxn];
int a[maxn];
void insert(int num){
int root = 0;
for(int i = 1;i <= num;i++){
int id = RD();
//cout<<id<<" ";
if(!trie[root][id])trie[root][id] = ++tot;//cout<<"tot="<<tot<<endl;}
vis[trie[root][id]]++;
root = trie[root][id];
}
end[root]++;
//cout<<endl;
}
int find(int num){
int root = 0;
int sum = 0;
for(int i = 1;i <= num;i++){
int id = a[i];
if(!trie[root][id])return sum;
root = trie[root][id];
sum += end[root];
}
if(end[root] != 0)sum -= end[root];
return vis[root] + sum;
}
int main(){
n = RD();m = RD();
for(int i = 1;i <= n;i++){
int num = RD();
insert(num);
}
for(int i = 1;i <= m;i++){
int num = RD();
for(int j = 1;j <= num;j++){
a[j] = RD();
}
cout<<find(num)<<endl;
}
return 0;
}
2018.5.9
学习了AC自动机以后,发现字典树写在一个结构体更直观,于是修改一下:
struct Trie{
int fail;
int v[26];
int end;
}trie[maxn];
int tot;
void insert(char s[]){
int len = strlen(s);
int now = 0;
for(int i = 0;i < len;i++){
int id = s[i] - 'a';
if(!trie[now].v[id])trie[now].v[id] = ++tot;
now = trie[now].v[id];
}
trie[now].end++;
}
upd 2018.5.10
妈的上面那个写法难看死了又难码,改回来