洛谷 P3966 [TJOI2013]单词(AC自动机 || Fail树)
题目链接:https://www.luogu.com.cn/problem/P3966
对于这一道题,可以对所有单词建出AC自动机,然后将每个节点x和fail[x]之间连一条边,这样可以形成一个树:fail树。
根据fail树的一些性质:
①:每个节点都是一个字符串的前缀,并且每个字符串的前缀一定在fail树上有一个节点
②:fail树的大小等于AC自动机的大小,fail树的根就是AC自动机的根
③:每个节点的父亲都是这个节点的最长后缀,每个节点的所有祖先是这个节点的所有后缀
------>尤其是第3个性质,我们可以把这个问题转化成:
只要将每个单词加入字典树,中间经过的节点sum值全部++,然后建立AC自动机求出fail树,从根节点开始DFS,对于树上每个节点,将它和它子树中所有的sum加起来就是该节点单词出现总次数。
AC代码:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #include<queue> 6 using namespace std; 7 const int N=1000000+100; 8 int fail[N],ch[N][26],vis[N],ans[N],sum[N],head[N]; 9 char str[N]; 10 int cnt,tot; 11 struct node{ 12 int to,next; 13 }edge[N*2]; 14 void init(){ 15 memset(head,-1,sizeof(head)); 16 tot=0; 17 } 18 void add(int u,int v){ 19 edge[tot].next=head[u]; 20 edge[tot].to=v; 21 head[u]=tot++; 22 } 23 void insert(char *s,int v){ 24 int u=0; 25 int len=strlen(s); 26 for(int i=0;i<len;i++){ 27 int id=s[i]-'a'; 28 if(!ch[u][id]) ch[u][id]=++cnt; 29 u=ch[u][id]; 30 sum[u]++; 31 } 32 vis[v]=u; 33 } 34 void get_fail(){ 35 int u=0; 36 queue<int> q; 37 for(int i=0;i<26;i++){ 38 if(ch[u][i]){ 39 q.push(ch[u][i]); 40 fail[ch[u][i]]=u; 41 add(0,ch[u][i]); 42 } 43 } 44 while(!q.empty()){ 45 int u=q.front();q.pop(); 46 for(int i=0;i<26;i++){ 47 if(ch[u][i]){ 48 q.push(ch[u][i]); 49 fail[ch[u][i]]=ch[fail[u]][i]; 50 add(fail[ch[u][i]],ch[u][i]); 51 } 52 else ch[u][i]=ch[fail[u]][i]; 53 } 54 } 55 } 56 void DFS(int u,int fa){ 57 for(int i=head[u];i!=-1;i=edge[i].next){ 58 int v=edge[i].to; 59 if(v==fa) continue; 60 DFS(v,u); 61 sum[u]+=sum[v]; 62 } 63 } 64 int main(){ 65 int n; 66 scanf("%d",&n); 67 for(int i=1;i<=n;i++){ 68 scanf("%s",str); 69 insert(str,i); 70 } 71 init(); 72 get_fail(); 73 DFS(0,-1); 74 for(int i=1;i<=n;i++) printf("%d\n",sum[vis[i]]); 75 return 0; 76 }