AC自动机 后缀数组 随笔
天天一头雾水,真是头大呢;
也祝自己生日快乐7.12,真是和海亮美好的回忆;
AC自动机:自动A题的机器;显然这样说是错的;
这个东西 我只想写一下自己的理解 然后一些总结;
首先 它是具有KMP性质的东西,但是又是有区别的;
KMP的Next数组是用来最长公共前后缀,而AC自动机的fail指针是用来搞相同后缀即可;
因为KMP只对一个模式串做匹配,而AC自动机要对多个模式串做匹配。
有可能 fail指针指向的结点对应着另一个模式串,两者前缀不同。
也就是说,AC自动机在对匹配串做逐位匹配时,同一位上可能匹配多个模式串。
因此 fail 指针会在字典树上的结点来回穿梭,而不像KMP在线性结构上跳转。
首先是将模式串插入trie树中,插入操作一样,注意打标记;
我们利用部分已经求出 fail 指针的结点推导出当前结点的 fail 指针。具体我们用BFS实现:
考虑字典树中当前的节点u,u的父节点是p,p通过字符c的边指向u。
假设深度小于u的所有节点的 fail指针都已求得。那么p的 fail指针显然也已求得。
我们跳转到p的 fail指针指向的结点 fail[p] ;
如果结点 fail[p]通过字母 c 连接到的子结点 w 存在:
则让u的fail指针指向这个结点 w ( fail[u]=w)。
相当于在 p 和 fail[p] 后面加一个字符 c ,就构成了 fail[u] 。
如果 fail[p]通过字母 c 连接到的子结点 w 不存在:
那么我们继续找到 fail[fail[p]] 指针指向的结点,重复上述判断过程,一直跳 fail 指针直到根节点。
如果真的没有,就令 fail[u]= 根节点。
然后建出AC自动机,搞出每个的fail指针,然后拿文本串在上面跑一下就行了;
模板一 ;
#include<bits/stdc++.h> using namespace std; template<typename T>inline void read(T &x) { x=0; register int f=1; register char ch=getchar(); while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } const int N=5000010; int n,m,trie[N][26],fail[N],e[N],idx; char s[1000005]; inline void insert(char *s) { int p=0; for(int i=0;s[i];i++) { int ch=s[i]-'a'; if(!trie[p][ch]) trie[p][ch]=++idx; p=trie[p][ch]; } e[p]++; } inline void build() { queue<int> q; memset(fail,0,sizeof(fail)); for(int i=0;i<26;i++) if(trie[0][i]) q.push(trie[0][i]); while(q.size()) { int k=q.front(); q.pop(); for(int i=0;i<26;i++) { if(trie[k][i]) { fail[trie[k][i]]=trie[fail[k]][i]; q.push(trie[k][i]); } else trie[k][i]=trie[fail[k]][i]; } } } int query(char *t) { int p=0,res=0; for(int i=0;t[i];i++) { p=trie[p][t[i]-'a']; for(int j=p;j&&~e[j];j=fail[j])res+=e[j],e[j]=-1; } return res; } int main() { read(n); for(int i=1;i<=n;i++) { scanf("%s",s); insert(s); } build(); scanf("%s",s); int ans=query(s); printf("%d\n",ans); }
#include<bits/stdc++.h> using namespace std; template<typename T>inline void read(T &x) { x=0; register int f=1; register char ch=getchar(); while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } string mob[300010]; int e[300010],trie[300010][26],fail[300010],ans[300010],n,idx; inline void insert(string s,int v) { int p=0; for(int i=0;i<s.size();i++) { int ch=s[i]-'a'; if(!trie[p][ch]) trie[p][ch]=++idx; p=trie[p][ch]; } e[p]=v; } inline void build() { queue<int> q; // memset(fail,0,sizeof(fail)); for(int i=0;i<26;i++) if(trie[0][i]) q.push(trie[0][i]); while(q.size()) { int k=q.front(); q.pop(); for(int i=0;i<26;i++) { if(trie[k][i]) { fail[trie[k][i]]=trie[fail[k]][i]; q.push(trie[k][i]); } else trie[k][i]=trie[fail[k]][i]; } } } inline void query(string s) { int p=0; for(int i=0;i<s.size();i++){ p=trie[p][s[i]-'a']; for(int j=p;j;j=fail[j]) ans[e[j]]++; } } int main() { // freopen("a.in","r",stdin); // freopen("a.out","w",stdout); while(scanf("%d",&n),n) { idx=0; // clear(); memset(e,0,sizeof(e)); memset(ans,0,sizeof(ans)); memset(trie,0,sizeof(trie)); memset(fail,0,sizeof(fail)); for(int i=1;i<=n;i++) { cin>>mob[i]; insert(mob[i],i); } build(); string t; cin>>t; query(t); int temp=0; for(int i=1;i<=n;i++)if(ans[i]>temp) temp=ans[i]; cout<<temp<<endl; for(int i=1;i<=n;i++)if(ans[i]==temp) cout<<mob[i]<<"\n"; } return 0; }
#include<bits/stdc++.h> using namespace std; const int N=5000010; template<typename T>inline void read(T &x) { x=0; register int f=1; register char ch=getchar(); while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } int n,idx,lin[200010],tot; int trie[200010][26],fail[200010],e[200010],size[200010]; string s,t; int ans[200010]; struct gg { int x,y,next; }a[N<<1]; inline void insert(string s,int v) { int p=0; for(int i=0;i<s.size();i++) { int ch=s[i]-'a'; if(!trie[p][ch]) trie[p][ch]=++idx; p=trie[p][ch]; } e[v]=p; } inline void build() { queue<int> q; memset(fail,0,sizeof(fail)); for(int i=0;i<26;i++) if(trie[0][i]) q.push(trie[0][i]); while(q.size()) { int k=q.front(); q.pop(); for(int i=0;i<26;i++) { if(trie[k][i]) { fail[trie[k][i]]=trie[fail[k]][i]; q.push(trie[k][i]); } else trie[k][i]=trie[fail[k]][i]; } } } inline void add(int x,int y) { a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; } inline void dfs(int x) { for(int i=lin[x];i;i=a[i].next) { int y=a[i].y; dfs(y); size[x]+=size[y]; } } int main() { // freopen("a.in","r",stdin); // freopen("a.out","w",stdout); read(n); for(int i=1;i<=n;i++) { cin>>s; insert(s,i); } build(); cin>>t; int p=0; for(int i=0;i<t.size();i++) { p=trie[p][t[i]-'a']; size[p]++; } for(int i=1;i<=idx;i++) add(fail[i],i);//建个fail树 dfs(0); for(int i=1;i<=n;i++) printf("%d\n",size[e[i]]); return 0; }
来几道比较有意思的题目:
这个题目没有文本串,我们可以想象出一个符合条件的文本串,如果存在,那么这个文本串一定不能在病毒串组成的trie上匹配,那么他一定会沿着fail指针一直跳,
怎么才能一直跳,有环,对啊,并且这里容易想明白的是,我们一定不能跳到一个有结束标记的节点;
但是直接写的话,我们一定会失败,为什么,我们考虑一种情况,如果在跳fail指针的时候,我们顺着fail指针调到一个有结束节点的地方,那么我们也是不能选择这个跳,
我们把这些情况预处理出来就可以了;dfs找环是否存在就好了;
#include<bits/stdc++.h> using namespace std; template<typename T>inline void read(T &x) { x=0; register int f=1; register char ch=getchar(); while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } int n,idx,flag,trie[3000010][26],e[3000010],fail[3000010],vis[3000010]; string s; inline void insert(string s) { int p=0; for(int i=0;i<s.size();i++) { int ch=s[i]-'0'; if(!trie[p][ch]) trie[p][ch]=++idx; p=trie[p][ch]; } e[p]=1; } inline void build() { queue<int> q; memset(fail,0,sizeof(fail)); for(int i=0;i<10;i++) if(trie[0][i]) q.push(trie[0][i]); while(q.size()) { int k=q.front();q.pop(); for(int i=0;i<10;i++) { if(trie[k][i]) { fail[trie[k][i]]=trie[fail[k]][i]; q.push(trie[k][i]); } else trie[k][i]=trie[fail[k]][i]; } } } inline void dfs(int x) { if(flag) return ; for(int i=0;i<=1;i++) { int p=trie[x][i]; if(!vis[p]&&!e[p]) { vis[p]=1; dfs(p); vis[p]=0;//还原现场; } else if(vis[p]&&!e[p]) { flag=1; return ; } } } int main() { // freopen("a.in","r",stdin); // freopen("a.out","w",stdout); read(n); for(int i=1;i<=n;i++) { cin>>s; insert(s); } build(); vis[0]=1; for(int i=1;i<=idx;i++) { int k=i,m=0; while(k>0) { if(e[k]) m=k; k=fail[k]; } k=i; if(m==0) continue; while(k>0) { if(k==m) break; e[k]=1; k=fail[k]; } } dfs(0); if(flag) { printf("TAK\n"); } else { printf("NIE\n"); } return 0; }
这个题目让我们找能匹配的最长前缀,显然我们预处理出来所有情况,在匹配的时候,当不能匹配的时候退出,此时就是最大长度;
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<queue> #include<deque> #include<cmath> #include<ctime> #include<cstdlib> #include<stack> #include<algorithm> #include<vector> #include<cctype> #include<utility> #include<set> #include<bitset> #include<map> #define INF 1000000000 #define ll long long #define min(x,y) ((x)>(y)?(y):(x)) #define max(x,y) ((x)>(y)?(x):(y)) #define RI register ll #define db double #define EPS 1e-8 using namespace std; #define mann 100005 #define maxn 10000005 template<typename T>inline void read(T &x) { x=0; register int f=1; register char ch=getchar(); while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } int n,m,idx; int trie[maxn][4],fail[maxn],e[maxn],f[maxn]; char s[maxn],d[4],b[mann][105]; inline char change(char c) { if(c=='E') return d[0]; else if(c=='S') return d[1]; else if(c=='W') return d[2]; else return d[3]; } inline void insert(char *s) { int p=0,len=strlen(s); for(int i=0;i<len;i++) { int ch=s[i]-'a'; if(!trie[p][ch]) trie[p][ch]=++idx; p=trie[p][ch]; } e[p]=1; } inline void build() { queue<int> q; memset(fail,0,sizeof(fail)); for(int i=0;i<4;i++) if(trie[0][i]) q.push(trie[0][i]); while(q.size()) { // cout<<"((()))"<<endl; int k=q.front(); q.pop(); for(int i=0;i<4;i++) { if(trie[k][i]) { fail[trie[k][i]]=trie[fail[k]][i]; q.push(trie[k][i]); } else trie[k][i]=trie[fail[k]][i]; } } } inline void query(char *s) { int len=strlen(s),p=0,k; for(int i=0;i<len;i++) { int ch=s[i]-'a'; k=trie[p][ch]; while(k>0) { if(f[k]) break;//不用再跳fail了,已被更新; f[k]=1; k=fail[k]; } p=trie[p][ch]; } } inline int ask(char *t) { int len=strlen(t),p=0; for(int i=0;i<len;i++) { int ch=t[i]-'a'; p=trie[p][ch]; if(!f[p]) return i; } return len; } int main() { // freopen("1.in","r",stdin); // freopen("a.out","w",stdout); d[0]='a',d[1]='b',d[2]='c',d[3]='d'; read(n); read(m); scanf("%s",s); for(int i=0;i<n;i++) { s[i]=change(s[i]); } for(int i=1;i<=m;i++) { scanf("%s",b[i]); int len=strlen(b[i]); for(int j=0;j<len;j++) b[i][j]=change(b[i][j]); insert(b[i]); } build(); query(s); for(int i=1;i<=m;i++) { cout<<ask(b[i])<<endl; } return 0; }
阿狸打字机,最近会做;
然后谈谈我对后缀数组的小理解,可能会有错误,以后会订正;
反正我也刚学这个东西,后缀自动机学了一点没来得及写题,一说到这里,我就想起来自己线性代数没写,数据结构没写,数论没写,啊啊啊;
算了,首先后缀数组(SA)是个好东西啊;
我们用基数排序是在O(n)的时间复杂度内对每个后缀进行排序;
倍增合并的话是logn的,总的复杂度变成了nlogn,比快排少一个log,还不错;
就是两个关键字搞来搞去,注释在代码里;
sa数组表示排名为i的后缀的起始下标;也就是这道题所求的答案;
x数组是第一关键字,y数组是第二关键字,c数组是桶;
我们先将x扔进桶里,排好之后将y扔进去;’
heigh数组表示排名为i的后缀和排名为i-1的后缀的lcp,(最长公共前缀);
设h[i]=height[rk[i]],同样的,height[i]=h[sa[i]];
有定理:h[i]>=h[i-1]-1;
不过这就是我以后写题后再来填坑吧;
#include<bits/stdc++.h> using namespace std; const int N=1000010; template<typename T>inline void read(T &x) { x=0; register int f=1; register char ch=getchar(); while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } int n,m,c[N],sa[N],x[N],y[N]; char s[N]; inline void get_sa() { m=122; for(int i=1;i<=n;i++) c[x[i]=s[i]]++;//建基数排序的桶 ,x[i]为i个字符的第一关键字 for(int i=2;i<=m;i++) c[i]+=c[i-1];// 做c的前缀和,为了求出每个关键字最多在第几名; for(int i=n;i>=1;i--) sa[c[x[i]]--]=i; for(int k=1;k<=n;k=k<<1) { int num=0;//一个计数器; for(int i=n-k+1;i<=n;i++) y[++num]=i;//y表示是第二关键字; //因为n-k+1位到第n位是空串,空串优先级最高,排名靠前 for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k; //如果排名为i的数 在数组中是否大于k; //如果(sa[i]>k) 他可以作为第二关键字,直接把它第一关键字的位置添加y即可; //这里i枚举的是排名,第二关键字靠前的先进; for(int i=1;i<=m;i++) c[i]=0;//清桶; for(int i=1;i<=n;i++) ++c[x[i]];// 此时x作为第一关键字已经更新过,直接丢尽桶; for(int i=2;i<=m;i++) c[i]+=c[i-1];//第一关键字排名为1-i的个数; for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0; //因为y是按照第二关键字排的,所以第二关键字靠后的,在第一关键字对应的桶中靠后; swap(x,y);//无脑交换 ,利用上次信息倍增向下走; x[sa[1]]=1,num=1; for(int i=2;i<=n;i++) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; if(num==n) break; m=num;//此时不需要122了; } for(int i=1;i<=n;i++) printf("%d ",sa[i]); } int main() { // freopen("a.in","r",stdin); // freopen("a.out","w",stdout); scanf("%s",s+1); n=strlen(s+1); get_sa(); // get_height(); return 0; }