BZOJ3881: [Coci2015]Divljak
AC自动机·Fail树
题解:
一开始想错了,还是对AC自动机的性质太不熟悉了。。。
以前没接触过Fail树,先来简单总结一下。
Fail树就是只保留AC自动机的fail指针并将其反向建出来的树。
对于Fail树上的节点,它代表的字符串是其子树中的节点代表的字符串的后缀。也就是下面的包含上面的。
考虑这样一个问题:假设给你若干个字符串,每次询问a在b中出现了多少次?可以把字符串建AC自动机,建Fail树,让b在AC自动机上走,走到的点+1,最后查询一下a在Fail树上的子树和即可。处理下一个询问之前别忘了把+1的点清零。
这道题的不同在于,询问是a在几个b中出现过,就是说a在一个b中出现很多次也只能贡献1。因此可以让b在AC自动机上走,走到的点+1,然后把走到的点按dfs序排序,相邻两个的lca处-1。查询a的子树和即是答案。
Code:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
const int N = 2000005;
int n,q; char str[N]; int tp[N];
int ch[N][26],fail[N],ptr[N],sz=1;
int pa[N][25],dep[N],pos[N],end[N],tim;
int insert(char s[]){
int x=1;
for(int i=0;s[i]!='\0';i++){
if(!ch[x][s[i]-'a']){
ch[x][s[i]-'a']=++sz;
}
x=ch[x][s[i]-'a'];
}
return x;
}
void acBuild(){
queue<int> q; q.push(1); int f;
while(!q.empty()){
int u=q.front(); q.pop();
for(int c=0;c<26;c++){
if(ch[u][c]){
int v=ch[u][c];
for(f=fail[u];f && !ch[f][c];f=fail[f]);
if(f) fail[v]=ch[f][c]; else fail[v]=1;
q.push(v);
}
else{
ch[u][c]=ch[fail[u]][c]?ch[fail[u]][c]:1;
}
}
}
}
struct Edge{
int to,next;
} e[N*2];
int head[N], ec;
void add(int a,int b){
// D(a); D(b); E;
ec++; e[ec].to=b; e[ec].next=head[a]; head[a]=ec;
}
void dfs(int u,int f){
pa[u][0]=f; dep[u]=dep[f]+1; pos[u]=++tim;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==f) continue;
dfs(v,u);
}
end[u]=tim;
}
void initLCA(){
for(int j=1;j<=21;j++){
for(int i=1;i<=sz;i++){
pa[i][j]=pa[pa[i][j-1]][j-1];
}
}
}
int LCA(int a,int b){
if(dep[a]<dep[b]) swap(a,b);
int cha=dep[a]-dep[b];
for(int j=21;j>=0;j--){
if(cha&(1<<j)) a=pa[a][j];
}
if(a!=b){
for(int j=21;j>=0;j--){
if(pa[a][j]!=pa[b][j]){
a=pa[a][j]; b=pa[b][j];
}
}
a=pa[a][0];
}
return a;
}
void failBuild(){
for(int i=2;i<=sz;i++) add(fail[i],i);
dfs(1,0); initLCA();
// for(int i=1;i<=sz;i++) D(i), D(pos[i]), E;
}
struct BIT{
int c[N], sz;
void init(int _sz){ sz=_sz; memset(c,0,sizeof(c)); }
void add(int x,int d){ while(x<=sz){ c[x]+=d; x+=x&(-x); } }
int sum(int x){ int ans=0; while(x){ans+=c[x];x-=x&(-x);} return ans; }
int sum(int x,int y){ return sum(y)-sum(x-1); }
} bit;
bool cmp(int a,int b){
return pos[a]<pos[b];
}
void work(char s[]){
int x=1; int tpsz=0; tp[tpsz++]=1;
for(int i=0;s[i]!='\0';i++){
x=ch[x][s[i]-'a'];
tp[tpsz++]=x;
}
sort(tp,tp+tpsz,cmp);
tpsz=unique(tp,tp+tpsz)-tp;
// D(tpsz); E;
// for(int i=0;i<tpsz;i++){ D(tp[i]); E; }
for(int i=0;i<tpsz;i++){
bit.add(pos[tp[i]],1);
if(i) bit.add(pos[LCA(tp[i-1],tp[i])],-1);
}
}
void solve(){
bit.init(sz);
cin>>q; int op,p;
while(q--){
// D(q); E;
scanf("%d",&op);
if(op==1){
scanf("%s",str);
work(str);
}
else{
scanf("%d",&p);
int l=pos[ptr[p]], r=end[ptr[p]];
// D(l); D(r); E;
printf("%d\n",bit.sum(l,r));
}
}
}
int main(){
freopen("a.in","r",stdin);
cin>>n;
for(int i=1;i<=n;i++){
scanf("%s",str);
ptr[i]=insert(str);
}
// D(sz); E;
acBuild();
// for(int i=1;i<=sz;i++) D(i), D(fail[i]), E;
failBuild();
solve();
}

本文详细介绍了AC自动机及Fail树的概念与应用。通过一个具体的题目实例,讲解了如何利用AC自动机进行字符串匹配,并结合Fail树解决特定问题的方法。
567

被折叠的 条评论
为什么被折叠?



