BZOJ3881 Coci2015 Divljak fail树+差分
题目大意,给出两个字符串集合S和T,向T中添加字符串,查询S_i在T中有几个字符串出现过。一看这种多字符串匹配问题,我们联想到了AC自动机,做法就是,对于S集合我们建立一个AC自动机,建出fail树,fail树有一个很好的性质就是,对于一个节点x,它所对应的字符串是它子树中所有节点对应的字符串的后缀。我们考虑如果S_x在P_x 中出现过,他肯定是P_x某一个前缀的后缀,所以我们把P_x在AC自动机上跑,跑到每一个节点我们更新一下他所在的fail树,统计答案的时候只需统计子树的大小就行了。但是这样会有一点小问题,就是会统计重复,如果对于每一个前缀我们都更新一下它到跟的路径和,这样会重复,因为S_x可能会在P_x中出现多次。实际上我们求的是一个点的子树所有链的并集,解决方案很巧妙,利用树上差分,我们按照dfs序排好顺序,然后相邻的两个节点的lca处-1就行了,这样就不会统计重复,利用树状数组维护一下dfs序即可。(有一点需要注意,在trie树上我们一共有tot个节点,那么对于fail树我们有tot+1个节点,树状数组大小注意一下,一开始错在了这里)——by VANE
#include<bits/stdc++.h> using namespace std; const int N=2000010; char s[N]; vector<int> g[N]; int son[N][30],fail[N],L[N],R[N],a[N]; int sum[N],pos[N],id[N],dep[N]; int mn[N*2][25],LOG[N*2]; int tot,n,dfn,cnt; int cmp(int x,int y) { return L[x]<L[y]; } void dfs(int u) { L[u]=++dfn; mn[pos[u]=++cnt][0]=u; for(int i=0;i<g[u].size();++i) { int v=g[u][i]; dep[v]=dep[u]+1; dfs(v); mn[++cnt][0]=u; } R[u]=dfn; } void insert(int x) { int l=strlen(s+1); int p=0; for(int j=1;j<=l;++j) { if(!son[p][s[j]-'a']) son[p][s[j]-'a']=++tot; p=son[p][s[j]-'a']; } id[x]=p; } void getfail() { queue<int> q; for(int i=0;i<26;++i) if(son[0][i]) q.push(son[0][i]); while(!q.empty()) { int u=q.front();q.pop(); for(int i=0;i<26;++i) if(son[u][i]) fail[son[u][i]]=son[fail[u]][i],q.push(son[u][i]); else son[u][i]=son[fail[u]][i]; } } int query(int x) { int res=0; for(;x;x-=x&-x) res+=sum[x]; return res; } int lca(int x,int y) { if(pos[x]<pos[y]) swap(x,y); int len=pos[x]-pos[y]+1; len=LOG[len]; return min(mn[pos[y]][len],mn[pos[x]-(1<<len)+1][len],cmp); } void add(int x,int w) { for(;x<=tot+1;x+=x&-x) sum[x]+=w; } int main() { scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%s",s+1); insert(i); } getfail(); for(int i=1;i<=tot;++i) g[fail[i]].push_back(i); dfs(0); for(int k=1;(1<<k)<=cnt;++k) LOG[1<<k]=k; for(int i=3;i<=cnt;++i) if(!LOG[i]) LOG[i]=LOG[i-1]; for(int k=1;k<=LOG[cnt];++k) for(int i=1;i+(1<<k)-1<=cnt;++i) mn[i][k]=min(mn[i][k-1],mn[i+(1<<k-1)][k-1],cmp); int q; scanf("%d",&q); while(q--) { int opt;scanf("%d",&opt); if(opt==1) { scanf("%s",s+1); int l=strlen(s+1); int p=0; for(int i=1;i<=l;++i) a[i]=p=son[p][s[i]-'a']; sort(a+1,a+1+l,cmp); for(int i=1;i<=l;++i) add(L[a[i]],1); for(int i=1;i<l;++i) { int x=lca(a[i],a[i+1]); add(L[x],-1); } } else { int x; scanf("%d",&x); printf("%d\n",query(R[id[x]])-query(L[id[x]]-1)); } } }
生命中真正重要的不是你遭遇了什么,而是你记住了哪些事,又是如何铭记的。