[COCI2011-2012#5] POPLOCAVANJE 后缀自动机
题面:洛谷
题解:
其实还可以用AC自动机做,但是没调出来,,,不知道发生了什么。。。
AC自动机做法如下:
观察到如果我们对给定的每个串建AC自动机,那么直接拿大串在上面匹配,如果遇到了一个单词的终止节点,假设当前大串的位置是i,匹配到的节点是j,那么这个单词覆盖了
[i - dep[j] + 1, i]这个区间(dep[j]即为单词长度)
但是我们发现空间根本开不下。
因为对每个串分别匹配不会影响结果,所以考虑每50个串我们重建一次AC自动机,然后匹配一次。
匹配中的每次区间修改都用差分维护,那么最后对于每个权值大于等于1的位置都统计1 的贡献即为答案。
后缀自动机做法如下:
考虑统计对于每个位置而言,以i为结尾的最长被覆盖的长度
我们对大串建立后缀自动机,然后对于每个小串都拿去自动机上匹配。假设我们最后匹配到的状态为x,既然这个状态已经被覆盖了,那么x在parent树上的子树也一定可以被覆盖。
但是要对每个状态内的最长串长度MAXS取min,因为匹配到一个状态只能代表可以覆盖这个状态所代表的子串,且长度不能大于当前给定图案(小串)的长度。
那么我们对每次匹配到的状态x打上标记,标记权值为当前小串的长度,如果有多个标记,取最大的那个。
最后统计打完所有标记之后,对于每个节点都下传标记,下传的过程中依然对标记权值取max。
最后即可得到对于每个位置而言,以i为结尾的最长被覆盖的长度。
一个快速(好写)统计的方法:
因为对于每个点下传标记就相当于对每个点接收来自它父亲的标记,那么我们只需要保证在接收点x的父亲的标记时,fa[x]已经接收过来自fa[fa[x]]的标记。
因此我们按bfs序来更新标记就可以了,因为l[fa[x]](l[i]表示状态i的MAXS,即最长的保证right集合不变的长度)一定小于l[x],所以我们按照l[x]的大小来确定顺序即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 301000 5 #define ac 601000 6 7 int n, m, ans; 8 int b[AC], k[ac]; 9 int d[AC], mark[ac];//差分数组 10 char s[AC]; 11 12 inline void upmax(int &a, int b){ 13 if(b > a) a = b; 14 } 15 16 struct sam_atm{ 17 int last, cnt; 18 int ch[ac][26], fa[ac], l[ac], right[ac]; 19 20 inline void add(int c, int i) 21 { 22 int p = last, np = ++ cnt; 23 last = np, l[np] = l[p] + 1, right[np] = i; 24 for( ; p && !ch[p][c]; p = fa[p]) ch[p][c] = np; 25 if(!p) fa[np] = 1; 26 else 27 { 28 int q = ch[p][c];//获取第一个有c的节点的对应边所指向的节点 29 if(l[p] + 1 == l[q]) fa[np] = q;//如果不会造成影响,那么就直接连fa 30 else 31 { 32 int nq = ++ cnt;//否则就再建新点 33 l[nq] = l[p] + 1; 34 memcpy(ch[nq], ch[q], sizeof(ch[q]));//把q的信息赋值给nq,,,接下来nq相当于要取代q了 35 fa[nq] = fa[q], fa[q] = fa[np] = nq; 36 for( ; ch[p][c] == q; p = fa[p]) ch[p][c] = nq; 37 } 38 } 39 } 40 41 void find() 42 { 43 int len = strlen(s + 1), now = 1; 44 for(R i = 1; i <= len; i ++) 45 { 46 if(!ch[now][s[i] - 'a']) return ; 47 now = ch[now][s[i] - 'a']; 48 } 49 upmax(mark[now], len); 50 } 51 52 void work() 53 { 54 for(R i = 1; i <= cnt; i ++) ++ b[l[i]]; 55 for(R i = 1; i <= n; i ++) b[i] += b[i - 1];//这里只需要枚举到n,多枚举就re了 56 for(R i = 1; i <= cnt; i ++) k[b[l[i]] --] = i;//为i赋b[l[i]]的排名 57 for(R i = 1; i <= cnt; i ++) upmax(mark[k[i]], mark[fa[k[i]]]); 58 59 for(R i = 1; i <= cnt; i ++) //只能用有right的叶子节点更新 60 if(right[i]) ++ d[right[i] - mark[i] + 1], -- d[right[i] + 1]; 61 for(R i = 1; i <= n; i ++) d[i] += d[i - 1]; 62 for(R i = 1; i <= n; i ++) ans += (d[i] > 0); 63 printf("%d\n", n - ans); 64 } 65 }T; 66 67 void pre() 68 { 69 T.last = T.cnt = 1;//这个又忘了。。。 70 scanf("%d%s", &n, s + 1); 71 for(R i = 1; i <= n; i ++) T.add(s[i] - 'a', i); 72 scanf("%d", &m); 73 for(R i = 1; i <= m; i ++) scanf("%s", s + 1), T.find(); 74 } 75 76 int main() 77 { 78 // freopen("in.in", "r", stdin); 79 pre(); 80 T.work(); 81 // fclose(stdin); 82 return 0; 83 }