bzoj3172: [Tjoi2013]单词
Time Limit: 10 Sec Memory Limit: 512 MB
某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。
Input
第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N<=200,单词长度不超过10^6
Output
输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。
Sample Input
3
a
aa
aaa
Sample Output
6
3
1
再是一道初识fail树的经典题,所谓fail树就是在构建完fail指针后,把所有的fail指针反向,变成一棵树,每个串终点所在fail树的子树中cnt的总和就是这个字符串的出现次数,于是每个点记录所在fail树的子树中cnt的数量,可以线性时间内出解
这一大段话有点难理解(如果你是初学者的话),实际上在实现起来不用真正的建一棵fail树,只要在makefail完成后
cnt[fail[heap[i]]]+=cnt[heap[i]];
heap记录的是bfs的序列
这里写代码片
#include<cstdio> ///fail树
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
int n;
char w[1000010];
int ch[1000010][30],tot=0,fail[1000010];
int cnt[1000010];
int word[256],tt=0,heap[1000010];
void build(int bh)
{
int i,j,nw=0;
int len=strlen(w);
for (i=0;i<len;i++)
{
int x=w[i]-'a';
if (!ch[nw][x]) ch[nw][x]=++tot;
nw=ch[nw][x];
cnt[nw]++;//新结点的经过次数++
}
word[bh]=nw;
return;
}
//这道题中字典和需要匹配的字符是一样的,所以不用单独再把字典中的东西跑一遍AC自动机
//在建trie时就把经过的点cnt++,相当于匹配了一遍
void make() //make fail
{
int i,j;
queue<int> q;
for (i=0;i<26;i++)
if (ch[0][i])
q.push(ch[0][i]);
while (!q.empty())
{
int r=q.front(); //当前节点
q.pop();
heap[++tt]=r;
for (i=0;i<26;i++)
{
// if (!ch[r][i]) 这种写法和我下面用//标注的写法
// { 时间复杂度是一样的,都是可行的
// ch[r][i]=ch[fail[r]][i]; 只是思维和处理方式不大一样
// continue;
// }
// fail[ch[r][i]]=ch[fail[r]][i];
if (!ch[r][i]) continue; //
int f=fail[r]; //
while(f&&!ch[f][i]) f=fail[f]; //
fail[ch[r][i]]=ch[f][i]; //
q.push(ch[r][i]);
}
}
for (i=tt;i;i--)
cnt[fail[heap[i]]]+=cnt[heap[i]];
return; //在进行这一步操作时一定要按照bfs的顺序!!!
}
int main()
{
// memset(cnt,0,sizeof(cnt)); 调用cstring进行memset操作超级费时
// memset(fail,0,sizeof(fail)); 我在下面会具体解释
// memset(ch,0,sizeof(ch));
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%s",&w);
build(i);
}
make();
for (int i=1;i<=n;i++)
printf("%d
",cnt[word[i]]);
return 0;
}
交到bzoj上发现我的用时要比其他人多很多,自行探究后发现是memset的guo
上面的那一行是使用memeset后的用时
下面这一行是去掉memset后的用时
可见,memset要慎用