存个模板
void get_SAM(int w) {//加入的字母-'a' //c[w]域是一个DAG 存的是转移边 也就是后缀自动机 而par域是存辅助建后缀自动机的parent树 int x=++ndnum,tmp=lasts; nd[x].len=nd[lasts].len+1; //一直向下跳 直到在parent树上找到第一个包含x转移边的right集合 for(;tmp&&!nd[tmp].c[w];tmp=nd[tmp].par) nd[tmp].c[w]=x;//将A以上的边连向x if(!tmp) nd[x].par=root;//如果不存在 x就直接连root else{//tmp相当于A int B=nd[tmp].c[w];//把转移后的集合记为B if(nd[B].len==nd[tmp].len+1) nd[x].par=B;//如果满足是一起分裂的 就将x合并入B else{//否则新建一个点 将两边连向这个点 int nB=++ndnum; nd[nB]=nd[B];//nB的所有边转移信息来自B nd[B].par=nd[x].par=nB;//连向新建的点 nd[nB].len=nd[tmp].len+1;//分裂极限是约束较大的A的len+1 // 往回走 将所有转移边原本连向B的指向nB 以后找x就可以通过nB来找 for(;tmp&&nd[tmp].c[w]==B;tmp=nd[tmp].par) nd[tmp].c[w]=nB; } } lasts=x; }
一道例题:后缀自动机求最小表示法
思路:利用性质:后缀自动机可以表示出一个串的所有子串。先把原串复制一遍,每次贪心地走最小的一个字符,如果没有,再向后for,一直走满len个
注意:len存的是后缀的长度,所以最终答案应该是nd[now].len-len+1 (要求的是最小表示的起始位置)
//#include<bits/stdc++.h> #include<stdio.h> #include<string.h> #include<algorithm> #include<math.h> using namespace std; #define N 10005 struct node{ int c[27],len,par; }nd[N*4]; char s[N*2]; int root=1,ndnum=1,lasts=1;//1!! void add(int w) {//加入的字母-'a' //c[w]域是一个DAG 存的是转移边 也就是后缀自动机 而par域是存辅助建后缀自动机的parent树 int x=++ndnum,tmp=lasts; nd[x].len=nd[lasts].len+1; //一直向下跳 直到在parent树上找到第一个包含x转移边的right集合 for(;tmp&&!nd[tmp].c[w];tmp=nd[tmp].par) nd[tmp].c[w]=x;//将A以上的边连向x if(!tmp) nd[x].par=root;//如果不存在 x就直接连root else{//tmp相当于A int B=nd[tmp].c[w];//把转移后的集合记为B if(nd[B].len==nd[tmp].len+1) nd[x].par=B;//如果满足是一起分裂的 就将x合并入B else{//否则新建一个点 将两边连向这个点 int nB=++ndnum; nd[nB]=nd[B];//nB的所有边转移信息来自B nd[B].par=nd[x].par=nB;//连向新建的点 nd[nB].len=nd[tmp].len+1;//分裂极限是约束较大的A的len+1 // 往回走 将所有转移边原本连向B的指向nB 以后找x就可以通过nB来找 for(;tmp&&nd[tmp].c[w]==B;tmp=nd[tmp].par) nd[tmp].c[w]=nB; } } lasts=x; } int main() { int T; scanf("%d",&T); while(T--){ memset(nd,0,sizeof(nd)); ndnum=1,lasts=1; scanf("%s",s); int len=strlen(s); for(int i=0;i<len;i++) add(s[i]-'a'); for(int i=0;i<len;i++) add(s[i]-'a'); int now=1; for(int i=1;i<=len;i++) for(int j=0;j<26;j++) if(nd[now].c[j]){ now=nd[now].c[j]; break; } printf("%d\n",nd[now].len-len+1); } }