BZOJ 3172 [Tjoi2013]单词 AC自动机Fail树

时间:2023-03-09 03:00:26
BZOJ 3172 [Tjoi2013]单词 AC自动机Fail树

题目链接:【http://www.lydsy.com/JudgeOnline/problem.php?id=3172】

题意:给出一个文章的所有单词,然后找出每个单词在文章中出现的次数,单词用标点符号隔开。

题解:时间很坑啊,朴素的做法是,先在AC自动机上插入单词,并记录下每个单词,然后用每个单词去维护答案。但是这样做时间空间都不允许,只有在插入的时候维护答案。过程是这样的,在插入的时候,每经过一个点,该节点的值加一,同时记录单词末尾的位置,然后在求玩Fail的时候,倒着走回去就可以了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2024 * 1024 + 15;
char tmp[maxn];
struct Aho_C
{
int Next[maxn][26], Fail[maxn], pos[maxn], cnt[maxn];
int que[maxn], st, ed;
int root, sz;
int newnode()
{
for(int i = 0; i < 26; i++) Next[sz][i] = 0;
cnt[sz] = 0;
sz++;
return sz - 1 ;
}
void init()
{
sz = 0;
root = newnode();
}
void Insert(char buf[], int id)
{
int len = strlen(buf);
int now = root;
for(int i = 0; i < len; i++)
{
if(!Next[now][buf[i] - 'a'])
Next[now][buf[i] - 'a'] = newnode();
now = Next[now][buf[i] - 'a'];
cnt[now]++;
}
pos[id] = now;
}
void getFail()
{
st = ed = 1;
for(int i = 0; i < 26; i++)
if(Next[root][i])
que[ed++] = Next[root][i];
while(st != ed)
{
int u = que[st++];
for(int i = 0; i < 26; i++)
{
int &v = Next[u][i];
if(!v)
{
v = Next[Fail[u]][i];
continue;
}
Fail[v] = Next[Fail[u]][i];
que[ed++] = v;
}
}
for(int i = ed - 1; i >= 1; i--) cnt[Fail[que[i]]] += cnt[que[i]];
}
} ac;
int main()
{
int N;
scanf("%d", &N);
ac.init();
for(int i = 1; i <= N; i++)
{
scanf("%s", tmp);
ac.Insert(tmp, i);
}
ac.getFail();
for(int i = 1; i <= N; i++) printf("%d\n", ac.cnt[ac.pos[i]]);
return 0;
}