【传送门:BZOJ3172】
简要题意:
给出n个字符串,求出每个字符串在这n个字符串中出现的次数
题解:
很容易就想到AC自动机,但是单单是AC自动机还不行
这时就要用AC自动机的延伸——fail树来做(时常膜一膜算法,有益身体健康)
fail树这个玩意就是将AC自动机中fail指针当成是一条边,然后建成一棵树
由于trie树上的每个点相当于一个字符串,所以这棵fail树满足假如有一个x点,x点的字符串一定是x点的子树中所有节点的字符串中的最长后缀(最长后缀也就是说明,字典树中在所有能够成为这些节点的后缀字符串长度,x点的字符串的长度最长)
这样子对于这道题而言,fail树简直就是神一般的存在QAQ
首先将每个字符串放进trie树里面,并且每经过一个trie树里的点,就用s数组记录这个字符串出现的个数,每经过一次就加一,然后构造AC自动机,然后在构造AC自动机的过程中,对每个点的fail指针进行建边,然后用dfs从根往下遍历,把s数组从下往上累加
然后在输入每个字符串的时候,记录每个字符串的最后一个字符在trie树上的编号,然后一个一个输出s[end[i]](end[i]表示第i个字符串最后一个字母在trie树上的编号)
参考代码:
#include<queue> #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #include<cmath> using namespace std; struct node { int c[27],fail; node() { fail=0; memset(c,-1,sizeof(c)); } }t[1100000]; int tot,n; char st[1100000]; int s[1100000]; int end[1100000]; void bt(int root,int z) { int x=root,len=strlen(st+1); for(int i=1;i<=len;i++) { int y=st[i]-‘a‘+1; if(t[x].c[y]==-1) { t[x].c[y]=++tot; } x=t[x].c[y];s[x]++; } end[z]=x; } struct edge { int x,y,next; }a[1100000];int len,last[1100000]; void ins(int x,int y) { len++; a[len].x=x;a[len].y=y; a[len].next=last[x];last[x]=len; } queue<int> q; void bfs() { int x; q.push(0); while(q.empty()==0) { x=q.front(); for(int i=1;i<=26;i++) { int son=t[x].c[i]; if(son==-1)continue; if(x==0) t[son].fail=0; else { int j=t[x].fail; while(j!=0&&t[j].c[i]==-1) j=t[j].fail; t[son].fail=max(t[j].c[i],0); } ins(t[son].fail,son); q.push(son); } q.pop(); } } void dfs(int x) { for(int k=last[x];k;k=a[k].next) { int y=a[k].y; dfs(y); s[x]+=s[y]; } } int main() { tot=0; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",st+1); bt(0,i); } len=0;memset(last,0,sizeof(last)); bfs(); dfs(0); for(int i=1;i<=n;i++) printf("%d\n",s[end[i]]); return 0; }