首页 > 代码库 > fail树
fail树
前置技能:AC自动机
假设我们有了一个AC自动机,然后在上面进行字符串匹配。
上面是一个有四个字符串的AC自动机(abcde、aacdf、cdf、cde),虚线是fail指针,实线是转移。
这是上一次讲AC自动机的时候的匹配代码:
int match(char* s){ int cur=rot,ans=0; for(int i=0;s[i];i++) { int c=s[i]-‘a‘; cur=ch[cur][c]; for(int f=cur;f!=rot;f=fail[f]) ans+=cnt[f], cnt[f]=0; } return ans;}
出题人嘿嘿一笑,给了你一个“aaaaaaaaaaaaaaaaaaa”。这样的字符串fail链长度为O(n)的,这就很尴尬了。
我们发现,如果我们把每个x与fail[x]连边,好像形成了一个树结构(fail[x]是x的父节点)。
我们就是要查询一个点到根路径上的cnt之和!我们只要dfs一下预处理出来就行了。
我们来分析一下这个树结构是什么东西。
首先,这棵树是在一个trie的基础上产生的,所以这棵树上的每个点都是一个字符串的前缀,而且每个字符串的每个前缀在这棵树上都对应着一个点。
其次,由于fail指针,每个点父节点的字符串都是这个点字符串的后缀,并且树上没有更长的它的后缀。
例1 bzoj3172 单词
给出n个字符串,询问每个字符串在所有字符串中的出现次数之和。
在建ac自动机的时候,我们把经过的那条链的cnt值全部+1,那么我们就是要查询子树和。
为什么?例如有一个串abcde,匹配了123abcde123,那么AC自动机上123abcde就会在abcde的子树中。
#include <iostream>#include <stdio.h>#include <math.h>#include <string.h>#include <time.h>#include <stdlib.h>#include <string>#include <bitset>#include <vector>#include <set>#include <map>#include <queue>#include <algorithm>#include <sstream>#include <stack>#include <iomanip>using namespace std;#define pb push_back#define mp make_pairtypedef pair<int,int> pii;typedef long long ll;typedef double ld;typedef vector<int> vi;#define fi first#define se second#define fe first#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}#define Edg int M=0,fst[SZ],vb[SZ],nxt[SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}#define Edgc int M=0,fst[SZ],vb[SZ],nxt[SZ],vc[SZ];void ad_de(int a,int b,int c){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;vc[M]=c;}void adde(int a,int b,int c){ad_de(a,b,c);ad_de(b,a,c);}#define es(x,e) (int e=fst[x];e;e=nxt[e])#define esb(x,e,b) (int e=fst[x],b=vb[e];e;e=nxt[e],b=vb[e])#define VIZ {printf("digraph G{\n"); for(int i=1;i<=n;i++) for es(i,e) printf("%d->%d;\n",i,vb[e]); puts("}");}#define VIZ2 {printf("graph G{\n"); for(int i=1;i<=n;i++) for es(i,e) if(vb[e]>=i)printf("%d--%d;\n",i,vb[e]); puts("}");}using namespace std;#define SZ 1000099int rot=1,ch[SZ][27],fail[SZ],e=1;ll cnt[SZ];int M=0,fst[SZ],vb[SZ+SZ],nxt[SZ+SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}int insert(char* s){ int cur=rot; for(int i=0;s[i];i++) { int c=s[i]-‘a‘; if(!ch[cur][c]) ch[cur][c]=++e; cur=ch[cur][c]; ++cnt[cur]; } return cur;}int qs[SZ],h=0,t=0;void bfail(){ h=t=0; fail[rot]=rot; for(int i=0;i<26;i++) { if(!ch[rot][i]) { ch[rot][i]=rot; continue; } fail[ch[rot][i]]=rot; qs[t++]=ch[rot][i]; } while(h!=t) { int cur=qs[h++]; for(int c=0;c<26;c++) { if(!ch[cur][c]) ch[cur][c]=ch[fail[cur]][c]; else { fail[ch[cur][c]]=ch[fail[cur]][c]; qs[t++]=ch[cur][c]; } } }}int n,T,orz[SZ];char str[SZ];void dfs(int x,int f=0){ for esb(x,e,b) { if(b==f) continue; dfs(b,x); cnt[x]+=cnt[b]; }}int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",str); orz[i]=insert(str); } bfail(); for(int i=2;i<=e;i++) adde(i,fail[i]); dfs(1); for(int i=1;i<=n;i++) printf("%lld\n",cnt[orz[i]]);}
例2 bzoj2434 阿狸的打字机
有一个打字机,上面有一个可以容纳字符串的凹槽。
打字机上有26个英文字母和‘B‘、‘P‘。输入字母,打字机的一个凹槽中会加入这个字母。按下‘B‘,打字机凹槽中会删掉最后一个字母。按下‘P‘,打字机会在纸上打印出凹槽中现有的所有字母并换行。按一下印有‘B‘的按键,打字机凹槽中最后一个字母会消失。
有m次询问,每次询问第x个打印的字符串在第y个打印的字符串中出现了多少次。
我们可以发现B就相当于回到trie上的父节点,P就相当于记录一下这个节点编号。
我们建出AC自动机和fail树。“第x个打印的字符串在第y个打印的字符串中出现了多少次”,如果树中只有第y个字符串那就是要统计x字符串这个点的子树和。
那么我们可以重新进行一次建树一样的操作,等到当前字符串为“第y个字符串”时再处理询问。我们只要在fail树上用dfs序+树状数组维护就行了。
#include <iostream>#include <stdio.h>#include <math.h>#include <string.h>#include <time.h>#include <stdlib.h>#include <string>#include <bitset>#include <vector>#include <set>#include <map>#include <queue>#include <algorithm>#include <sstream>#include <stack>#include <iomanip>using namespace std;#define pb push_back#define mp make_pairtypedef pair<int,int> pii;typedef long long ll;typedef double ld;typedef vector<int> vi;#define fi first#define se second#define fe first#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}#define Edg int M=0,fst[SZ],vb[SZ],nxt[SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}#define Edgc int M=0,fst[SZ],vb[SZ],nxt[SZ],vc[SZ];void ad_de(int a,int b,int c){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;vc[M]=c;}void adde(int a,int b,int c){ad_de(a,b,c);ad_de(b,a,c);}#define es(x,e) (int e=fst[x];e;e=nxt[e])#define esb(x,e,b) (int e=fst[x],b=vb[e];e;e=nxt[e],b=vb[e])#define VIZ {printf("digraph G{\n"); for(int i=1;i<=n;i++) for es(i,e) printf("%d->%d;\n",i,vb[e]); puts("}");}#define VIZ2 {printf("graph G{\n"); for(int i=1;i<=n;i++) for es(i,e) if(vb[e]>=i)printf("%d--%d;\n",i,vb[e]); puts("}");}using namespace std;#define SZ 233333int rot=1,ch[SZ][27],fail[SZ],fa[SZ],e=1;int M=0,fst[SZ],vb[SZ+SZ],nxt[SZ+SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}char str[SZ];int al(int& s) {if(!s) s=++e; return s;}int n,rp[SZ];void pre(char* s){ int cur=rot; for(int i=0;s[i];i++) { if(s[i]==‘B‘) cur=fa[cur]; else if(s[i]==‘P‘) rp[++n]=cur; else { char c=s[i]-‘a‘; int nx=al(ch[cur][c]); fa[nx]=cur; cur=nx; } }}int qs[SZ],h=0,t=0;void bfail(){ h=t=0; fail[rot]=rot; for(int i=0;i<26;i++) { if(!ch[rot][i]) { ch[rot][i]=rot; continue; } fail[ch[rot][i]]=rot; qs[t++]=ch[rot][i]; } while(h!=t) { int cur=qs[h++]; for(int c=0;c<26;c++) { if(!ch[cur][c]) ch[cur][c]=ch[fail[cur]][c]; else { fail[ch[cur][c]]=ch[fail[cur]][c]; qs[t++]=ch[cur][c]; } } } for(int i=2;i<=e;i++) adde(i,fail[i]);}int dfn[SZ],D=0,ls[SZ];void dfs(int x,int f=0){ dfn[x]=++D; for esb(x,e,b) { if(b==f) continue; dfs(b,x); } ls[x]=D;}int ss[SZ];int sum(int x){ int ans=0; for(;x>=1;x-=x&-x) ans+=ss[x]; return ans;}void edt(int x,int y){ for(;x<=D;x+=x&-x) ss[x]+=y;}int m,qa[SZ],qb[SZ],nq[SZ],fq[SZ],anss[SZ];int main(){ scanf("%s",str); pre(str); bfail(); scanf("%d",&m); for(int i=1;i<=m;i++) { scanf("%d%d",qa+i,qb+i); nq[i]=fq[qb[i]]; fq[qb[i]]=i; } dfs(rot); int cur=rot,ci=0; for(int i=0;str[i];i++) { if(str[i]==‘B‘) edt(dfn[cur],-1), cur=fa[cur]; else if(str[i]==‘P‘) { ++ci; for(int q=fq[ci];q;q=nq[q]) anss[q]=sum(ls[rp[qa[q]]])-sum(dfn[rp[qa[q]]]-1); } else { char c=str[i]-‘a‘; int nx=ch[cur][c]; fa[nx]=cur; cur=nx; edt(dfn[cur],1); } } for(int i=1;i<=m;i++) printf("%d\n",anss[i]);}
例3 bzoj3881 Divljak
Alice有n个字符串s1...sn,Bob有一个字符串集合,一开始集合是空的。
若干个操作,每个操作是往集合里添加一个字符串或者给定x,查询集合中有多少个字符串包含sx。
注意这里要求的是有多少个包含,而不是出现了几次。
例如ababab和ab,如果算子树和的话就会被多算一次。
我们考虑上一题我们实际上干的事情是对于每个前缀对应的点,把根到这个点的路径全部+1。
那为了不重复统计,我们只要保证没有点被多次+1就行了。
上一题我们转化为了子树和,那么这一题转化为子树和只要在每个重复的lca处-1就行了,具体实现就类似虚树那样用一个栈来维护。
好像把虚树的板子抄下来就过了
#include <iostream>#include <stdio.h>#include <math.h>#include <string.h>#include <time.h>#include <stdlib.h>#include <string>#include <bitset>#include <vector>#include <set>#include <map>#include <queue>#include <algorithm>#include <sstream>#include <stack>#include <iomanip>using namespace std;#define pb push_back#define mp make_pairtypedef pair<int,int> pii;typedef long long ll;typedef double ld;typedef vector<int> vi;#define fi first#define se second#define fe first#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}#define Edg int M=0,fst[SZ],vb[SZ],nxt[SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}#define Edgc int M=0,fst[SZ],vb[SZ],nxt[SZ],vc[SZ];void ad_de(int a,int b,int c){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;vc[M]=c;}void adde(int a,int b,int c){ad_de(a,b,c);ad_de(b,a,c);}#define es(x,e) (int e=fst[x];e;e=nxt[e])#define esb(x,e,b) (int e=fst[x],b=vb[e];e;e=nxt[e],b=vb[e])#define VIZ {printf("digraph G{\n"); for(int i=1;i<=n;i++) for es(i,e) printf("%d->%d;\n",i,vb[e]); puts("}");}#define VIZ2 {printf("graph G{\n"); for(int i=1;i<=n;i++) for es(i,e) if(vb[e]>=i)printf("%d--%d;\n",i,vb[e]); puts("}");}using namespace std;#define SZ 2000099int rot=1,ch[SZ][27],fail[SZ],e=1;int M=0,fst[SZ],vb[SZ+SZ],nxt[SZ+SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}int insert(char* s){ int cur=rot; for(int i=0;s[i];i++) { int c=s[i]-‘a‘; if(!ch[cur][c]) ch[cur][c]=++e; cur=ch[cur][c]; } return cur;}int qs[SZ],h=0,t=0;void bfail(){ h=t=0; fail[rot]=rot; for(int i=0;i<26;i++) { if(!ch[rot][i]) { ch[rot][i]=rot; continue; } fail[ch[rot][i]]=rot; qs[t++]=ch[rot][i]; } while(h!=t) { int cur=qs[h++]; for(int c=0;c<26;c++) { if(!ch[cur][c]) ch[cur][c]=ch[fail[cur]][c]; else { fail[ch[cur][c]]=ch[fail[cur]][c]; qs[t++]=ch[cur][c]; } } }}#define S 22int n,T,up[SZ][S],dep[SZ],orz[SZ];int dfn[SZ],ls[SZ],D=0;char str[SZ];void dfs(int x,int f=0){ dfn[x]=++D; up[x][0]=f; for(int i=1;i<=S-1;i++) up[x][i]=up[up[x][i-1]][i-1]; for esb(x,e,b) { if(b==f) continue; dep[b]=dep[x]+1; dfs(b,x); } ls[x]=D;}int jump(int x,int d){ for(int i=S-1;i>=0;i--) { if(up[x][i]&&dep[up[x][i]]>=d) x=up[x][i]; } return x;}int lca(int a,int b){ if(dep[a]>dep[b])swap(a,b); //dep[a]<=dep[b] b=jump(b,dep[a]); if(a==b) return a; for(int i=S-1;i>=0;i--) { if(up[a][i]==up[b][i]) continue; a=up[a][i]; b=up[b][i]; } return up[a][0];}int bs[SZ];int sum(int x){ int ans=0; for(;x>=1;x-=x&-x) ans+=bs[x]; return ans;}int sum(int l,int r){return sum(r)-sum(l-1);}void edt(int x,int y){ for(;x<=D;x+=x&-x) bs[x]+=y;}int ss[SZ],sn=0,vfa[SZ];bool cmpdfn(int a,int b){ if(a!=b) return dfn[a]<dfn[b]; return a<b;}int st[SZ],stn=0;int vs[SZ],vn=0;void inss(char* s){ int l=strlen(s),cur=1; sn=stn=vn=0; ss[++sn]=1; for(int i=0;i<l;i++) { cur=ch[cur][s[i]-‘a‘]; ss[++sn]=cur; } sort(ss+1,ss+1+sn,cmpdfn); sn=unique(ss+1,ss+1+sn)-ss-1; for(int i=1;i<=sn;i++) vs[++vn]=ss[i]; for(int i=1;i<=sn;i++) { int x=ss[i]; if(!stn) {st[++stn]=x; vfa[x]=0; continue;} int lc=lca(x,st[stn]); while(stn&&dep[st[stn]]>dep[lc]) { if(dep[st[stn-1]]<=dep[lc]) vfa[st[stn]]=lc; --stn; } if(st[stn]!=lc) { vs[++vn]=lc; vfa[lc]=st[stn]; st[++stn]=lc; } vfa[x]=lc; st[++stn]=x; } for(int i=1;i<=vn;i++) { int v=vs[i]; if(!vfa[v]) continue; edt(dfn[v],1); edt(dfn[vfa[v]],-1); }}int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",str); orz[i]=insert(str); } bfail(); for(int i=2;i<=e;i++) adde(i,fail[i]); dfs(1); int q; scanf("%d",&q); while(q--) { int g,x; scanf("%d",&g); if(g==1) scanf("%s",str), inss(str); else scanf("%d",&x), printf("%d\n",sum(dfn[orz[x]],ls[orz[x]])); }}
fail树