后缀自动机/回文自动机/AC自动机/序列自动机----各种自动机(自冻鸡) 题目泛做
题目1 BZOJ 3676 APIO2014 回文串
算法讨论:
cnt表示回文自动机上每个结点回文串出现的次数。这是回文自动机的定义考查题。
1 #include <cstdlib> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <iostream> 6 7 using namespace std; 8 9 const int C = 26; 10 const int N = 300000 + 5; 11 typedef long long ll; 12 13 char str[N]; 14 int len; 15 ll ans; 16 17 struct State { 18 int len, num, cnt, s, fail, next[C]; 19 }st[N]; 20 21 struct PTree { 22 int n, sz, last; 23 24 int newnode(int len) { 25 st[sz].len = len; 26 return sz ++; 27 } 28 29 void Init() { 30 n = sz = last = 0; 31 newnode(0); newnode(-1); 32 st[n].s = -1; 33 st[0].fail = 1; 34 } 35 36 int getfail(int x) { 37 while(st[n - st[x].len - 1].s != st[n].s) x = st[x].fail; 38 return x; 39 } 40 41 void count() { 42 for(int i = sz - 1; i >= 0; -- i) 43 st[st[i].fail].cnt += st[i].cnt; 44 } 45 46 void add(int c) { 47 st[++ n].s = c; 48 int cur = getfail(last); 49 if(!st[cur].next[c]) { 50 int now = newnode(st[cur].len + 2); 51 st[now].fail = st[getfail(st[cur].fail)].next[c]; 52 st[cur].next[c] = now; 53 st[now].num = st[st[now].fail].num + 1; 54 } 55 last = st[cur].next[c]; 56 st[last].cnt ++; 57 } 58 }ptree; 59 60 int main() { 61 scanf("%s", str); 62 len = strlen(str); 63 ptree.Init(); 64 for(int i = 0; i < len; ++ i) 65 ptree.add(str[i] - 'a'); 66 ptree.count(); 67 for(int i = 0; i < ptree.sz; ++ i) { 68 ans = max(ans, 1LL * st[i].cnt * st[i].len); 69 } 70 printf("%lld\n", ans); 71 return 0; 72 }
题目2 BZOJ2565 最长双回文串
算法讨论:
我们考虑计算出每个位置分别作为左右端点时最长回文串的长度。这样答案就是Max{r[i] + l[i + 1]}
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cstdlib> using namespace std; const int C = 26; const int N = 100000 + 5; struct State { int len, cnt, num, s, fail, next[C]; void clear() { len = cnt = num = s = fail = 0; memset(next, 0, sizeof next); } }st[N]; int len, r[N], l[N], ans; char str[N]; struct Ptree { int n, sz, last; int newnode(int len) { st[sz].len = len; return sz ++; } void clear() { for(int i = 0; i <= n; ++ i) st[i].clear(); } void Init() { n = sz = last = 0; newnode(0); newnode(-1); st[n].s = -1; st[0].fail = 1; } int getfail(int x) { while(st[n - st[x].len - 1].s != st[n].s) x = st[x].fail; return x; } void count() { for(int i = sz - 1; i >= 0; -- i) st[st[i].fail].cnt += st[i].cnt; } void add(int c) { st[++ n].s = c; int cur = getfail(last); if(!st[cur].next[c]) { int now = newnode(st[cur].len + 2); st[now].fail = st[getfail(st[cur].fail)].next[c]; st[cur].next[c] = now; st[now].num = st[st[now].fail].num + 1; } last = st[cur].next[c]; st[last].cnt ++; } }pt; int main() { scanf("%s", str); len = strlen(str); pt.Init(); for(int i = 0; i < len; ++ i) { pt.add(str[i] - 'a'); r[i] = st[pt.last].len; } pt.clear(); pt.Init(); reverse(str, str + len); for(int i = 0; i < len; ++ i) { pt.add(str[i] - 'a'); l[len - i - 1] = st[pt.last].len; } for(int i = 0; i < len; ++ i) ans = max(ans, r[i] + l[i + 1]); printf("%d\n", ans); return 0; }
题目3 BZOJ2160 拉拉队排练
算法讨论:
首先建出回文自动机,然后排序,然后计算。话说这个题并不用这么麻烦的,好像Manacher直接就上,但是为了练习回文自动机嘛 。
1 #include <cstdlib> 2 #include <cstring> 3 #include <cstdio> 4 #include <iostream> 5 #include <algorithm> 6 7 using namespace std; 8 9 const int N = 1000000 + 5; 10 const int C = 26; 11 const int mod = 19930726; 12 typedef long long ll; 13 14 ll k, ans = 1, cs[N]; 15 int len, rank[N]; 16 char str[N]; 17 18 struct State { 19 int len, cnt, num, s, fail, next[C]; 20 }st[N]; 21 22 struct Ptree { 23 int n, sz, last; 24 25 int newnode(int lens) { 26 st[sz].len = lens; 27 return sz ++; 28 } 29 30 void Init() { 31 sz = n = last = 0; 32 newnode(0); newnode(-1); 33 st[n].s = -1; 34 st[0].fail = 1; 35 } 36 37 int getfail(int x) { 38 while(st[n - st[x].len - 1].s != st[n].s) x = st[x].fail; 39 return x; 40 } 41 42 void count() { 43 for(int i = sz - 1; i >= 0; -- i) 44 st[st[i].fail].cnt += st[i].cnt; 45 } 46 47 void add(int c) { 48 st[++ n].s = c; 49 int cur = getfail(last); 50 if(!st[cur].next[c]) { 51 int now = newnode(st[cur].len + 2); 52 st[now].fail = st[getfail(st[cur].fail)].next[c]; 53 st[cur].next[c] = now; 54 st[now].num = st[st[now].fail].num + 1; 55 } 56 last = st[cur].next[c]; 57 st[last].cnt ++; 58 } 59 }pt; 60 61 ll quickpow(ll a, ll b) { 62 ll res = 1; 63 if(!b) return res; 64 while(b) { 65 if(b & 1) res = res * a % mod; 66 a = a * a % mod; 67 b >>= 1; 68 } 69 return res; 70 } 71 72 int main() { 73 scanf("%d%lld", &len, &k); 74 scanf("%s", str); 75 pt.Init(); 76 for(int i = 0; i < len; ++ i) 77 pt.add(str[i] - 'a'); 78 pt.count(); 79 for(int i = 0; i < pt.sz; ++ i) cs[st[i].len] ++; 80 for(int i = 1; i <= len; ++ i) cs[i] += cs[i - 1]; 81 for(int i = 0; i < pt.sz; ++ i) rank[cs[st[i].len] --] = i; 82 for(int i = pt.sz - 1; i; -- i) { 83 if(!k) break; 84 if(st[rank[i]].len & 1) { 85 if(k >= st[rank[i]].cnt) { 86 ans = 1LL * ans * quickpow(st[rank[i]].len, st[rank[i]].cnt) % mod; 87 k -= st[rank[i]].cnt; 88 } 89 else { 90 ans = 1LL * ans * quickpow(st[rank[i]].len, k) % mod; 91 k -= k; 92 } 93 } 94 } 95 printf("%lld\n", ans % mod); 96 return 0; 97 }
题目4 BZOJ2084
算法讨论:
首先满足条件的串对应位上的数字正好是相反的,而且一定是偶数长度的串。
变种Manacher(其实是暴力)
1 #include <cstdlib> 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 #include <algorithm> 6 7 using namespace std; 8 9 const int N = 500000 + 5; 10 typedef long long ll; 11 12 ll ans = 0; 13 int len, Mp[N << 1]; 14 char str[N], Ma[N << 1]; 15 16 int idx(char c) { 17 return c - '0'; 18 } 19 20 void Manacher(char *s, int lens) { 21 int l = 0; 22 Ma[l ++] = '$'; Ma[l ++] = '#'; 23 for(int i = 0; i < lens; ++ i) { 24 Ma[l ++] = s[i]; Ma[l ++] = '#'; 25 } 26 Ma[l] = '!'; 27 int Mx = 0, id = 0; 28 for(int i = 0; i < l; ++ i) { 29 /* 30 if(Mx > i) { 31 Mp[i] = min(Mp[2 * id - i], Mx - i); 32 } 33 else { 34 Mp[i] = 1; 35 } 36 */ 37 Mp[i] = 1; 38 while((Ma[i - Mp[i]] == Ma[i + Mp[i]] && Ma[i - Mp[i]] == '#') || ((Ma[i - Mp[i]] != '#' && Ma[i + Mp[i]] != '#' && (idx(Ma[i - Mp[i]]) ^ idx(Ma[i + Mp[i]])) == 1))) Mp[i] ++; 39 if(Mx < Mp[i] + i) { 40 Mx = Mp[i] + i; 41 id = i; 42 } 43 } 44 for(int i = 1; i < l; ++ i) { 45 if(Ma[i] == '#') { 46 ans = (long long) ans + (Mp[i] - 1) / 2; 47 } 48 } 49 printf("%lld\n", ans); 50 } 51 52 int main() { 53 scanf("%d%s", &len, str); 54 Manacher(str, strlen(str)); 55 56 return 0; 57 }
题目5 Uva719 GlassBeads
题目大意:求一个串的字典序最小的循环串。
算法讨论:设原串长为S,把原串倍长后建立出SAM,然后在Sam上走S步,所在的位置就是最小循环串的开头。
代码:
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdlib> 4 #include <cstdio> 5 #include <cstring> 6 #include <cassert> 7 using namespace std; 8 const int L = 10000 + 5; 9 10 int ans = 0; 11 char str[L<<2]; 12 13 struct State{ 14 int len, pre; 15 int next[26]; 16 17 State(){ 18 len = pre = 0; 19 memset(next, 0, sizeof next); 20 } 21 void clear(){ 22 len = pre = 0; 23 memset(next, 0, sizeof next); 24 } 25 }st[L<<2]; 26 27 struct SuffixAutomaton{ 28 int sz, last; 29 30 void Init(){ 31 last = 0; 32 sz = 0; 33 for(int i = 0; i < (L<<2); ++ i) 34 st[i].clear(); 35 st[0].len = 0; st[0].pre = -1; 36 sz ++; 37 } 38 void Extend(int c){ 39 int cur = sz ++; 40 st[cur].len = st[last].len + 1; 41 int p; 42 43 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 44 st[p].next[c] = cur; 45 46 if(p == -1) st[cur].pre = 0; 47 else{ 48 int q = st[p].next[c]; 49 if(st[q].len == st[p].len + 1) st[cur].pre = q; 50 else{ 51 int cle = sz ++; 52 st[cle].len = st[p].len + 1; 53 st[cle].pre = st[q].pre; 54 for(int i = 0; i < 26; ++ i) st[cle].next[i] = st[q].next[i]; 55 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 56 st[p].next[c] = cle; 57 st[q].pre = st[cur].pre = cle; 58 } 59 } 60 last = cur; 61 } 62 }SAM; 63 64 void Input(){ 65 scanf("%s", str); 66 } 67 68 void Output(){ 69 printf("%d\n", ans); 70 } 71 72 void Solve(){ 73 int len = strlen(str); 74 SAM.Init(); 75 for(int i = 0; i < len; ++ i) 76 str[i + len] = str[i]; 77 for(int i = 0; i < (len<<1); ++ i){ 78 SAM.Extend(str[i] - 'a'); 79 } 80 int p = 0; 81 for(int i = 0; i < len; ++ i){ 82 for(int j = 0; j < 26; ++ j){ 83 if(st[p].next[j]){ 84 p = st[p].next[j]; 85 break; 86 } 87 } 88 } 89 90 ans = st[p].len - len + 1; 91 } 92 93 int main(){ 94 int t; 95 scanf("%d", &t); 96 while(t --){ 97 Input(); 98 Solve(); 99 Output(); 100 } 101 return 0; 102 }
题目6 SPOJ LCS
题目大意:求两个串的最长公共子串。
算法讨论:把串1建立出SAM,串2在SAM上走。如果串2的当前字符在SAM上有出边,则len++,继续向下走。如果失配,则向后缀连接跳。
如果跳到-1,说明串1中没有这个字符,len变为0,从下一个开始走,如果有,就让Len 等于后缀连接点的Len+1,(这说明这之前的字符是可以匹配的)
然后再向下走一步。继续。
代码:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 #include <algorithm> 5 #include <cstdio> 6 7 using namespace std; 8 9 const int N = 250000 + 5; 10 const int C = 26; 11 12 struct State { 13 int len, pre, next[C]; 14 void clear() { 15 len = pre = 0; 16 memset(next, 0, sizeof next); 17 } 18 }st[N << 1]; 19 20 struct SuffixAutomaton { 21 int sz, last; 22 23 void Init() { 24 for(int i = 0; i < (N << 1); ++ i) 25 st[i].clear(); 26 sz = last = 1; 27 st[sz].len = 0; st[sz].pre = -1; 28 sz ++; 29 } 30 31 void add(int c) { 32 int cur = sz ++, p; 33 st[cur].len = st[last].len + 1; 34 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 35 st[p].next[c] = cur; 36 if(p == -1) st[cur].pre = 1; 37 else { 38 int q = st[p].next[c]; 39 if(st[q].len == st[p].len + 1) st[cur].pre = q; 40 else { 41 int cle = sz ++; 42 st[cle].pre = st[q].pre; 43 st[cle].len = st[p].len + 1; 44 for(int i = 0; i < C; ++ i) 45 st[cle].next[i] = st[q].next[i]; 46 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 47 st[p].next[c] = cle; 48 st[q].pre = st[cur].pre = cle; 49 } 50 } 51 last = cur; 52 } 53 }sam; 54 55 char str1[N], str2[N]; 56 int len1, len2; 57 58 int main() { 59 scanf("%s%s", str1, str2); 60 len1 = strlen(str1); len2 = strlen(str2); 61 sam.Init(); 62 for(int i = 0; i < len1; ++ i) 63 sam.add(str1[i] - 'a'); 64 int ans = 0, p = 1, len = 0; 65 for(int i = 0; i < len2; ++ i) { 66 int idx = str2[i] - 'a'; 67 if(st[p].next[idx]) { 68 len ++; 69 p = st[p].next[idx]; 70 } 71 else { 72 while(p != -1 && !st[p].next[idx]) 73 p = st[p].pre; 74 if(p == -1) { 75 p = 1; len = 0; 76 } 77 else { 78 len = st[p].len + 1; 79 p = st[p].next[idx]; //这句话没有写。 80 } 81 } 82 ans = max(ans, len); 83 } 84 printf("%d\n", ans); 85 return 0; 86 }
题目7 SPOJ LCS2
题目大意:求多个串的最长公共子串。
算法讨论:首先,两两之前匹配,然后取min的做法是错误的。正确的思路应该是,把其中的一个串建立SAM,然后把其它的串放在上面跑,对于SAM的每个结点,要多
维护两个信息,一个是当前串匹配的最大值,一个是已经匹配完的串到当前点匹配的最小值,每次匹配完一个串之后,用按照拓扑序,用儿子更新父亲的最大值,让其可能的大。
最后在所有最小值中取一个最大值,就是答案。
代码:
1 #include <cstdlib> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 #include <iostream> 6 7 using namespace std; 8 9 const int N = 100000 + 5; 10 const int C = 26; 11 12 struct State { 13 int pre, len, next[C], nl, ml; 14 }st[N << 1]; 15 16 struct SuffixAutomaton { 17 int sz, last; 18 19 void Init() { 20 sz = last = 1; 21 st[sz].pre = -1; st[sz].len = 0; 22 st[sz].ml = 0; 23 sz ++; 24 } 25 26 void add(int c) { 27 int cur = sz ++, p; 28 st[cur].len = st[last].len + 1; 29 st[cur].ml = st[last].ml + 1; 30 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 31 st[p].next[c] = cur; 32 if(p == -1) st[cur].pre = 1; 33 else { 34 int q = st[p].next[c]; 35 if(st[q].len == st[p].len + 1) st[cur].pre = q; 36 else { 37 int cle = sz ++; 38 st[cle].pre = st[q].pre; 39 st[cle].len = st[p].len + 1; 40 st[cle].ml = st[p].ml + 1; 41 for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i]; 42 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 43 st[p].next[c] = cle; 44 st[q].pre = st[cur].pre = cle; 45 } 46 } 47 last = cur; 48 } 49 }sam; 50 51 char str[N]; 52 int ans, lens, cs[N], rk[N << 1]; 53 54 int main() { 55 int p, len; 56 scanf("%s", str); 57 lens = strlen(str); 58 sam.Init(); 59 for(int i = 0; i < lens; ++ i) 60 sam.add(str[i] - 'a'); 61 for(int i = 1; i < sam.sz; ++ i) cs[st[i].len] ++; 62 for(int i = 1; i <= lens; ++ i) cs[i] += cs[i - 1]; 63 for(int i = 1; i < sam.sz; ++ i) rk[cs[st[i].len] --] = i; 64 65 while(scanf("%s", str) != EOF) { 66 lens = strlen(str); 67 p = 1; len = 0; 68 for(int i = 0; i < lens; ++ i) { 69 int idx = str[i] - 'a'; 70 if(st[p].next[idx]) { 71 len ++; 72 p = st[p].next[idx]; 73 } 74 else { 75 while(p != -1 && !st[p].next[idx]) 76 p = st[p].pre; 77 if(p == -1) { 78 p = 1; len = 0; 79 } 80 else { 81 len = st[p].len + 1; 82 p = st[p].next[idx]; 83 } 84 } 85 st[p].nl = max(len, st[p].nl); 86 } 87 for(int i = sam.sz - 1; i >= 1; -- i) { 88 st[rk[i]].ml = min(st[rk[i]].ml, st[rk[i]].nl); 89 if(st[rk[i]].pre != -1) { 90 st[st[rk[i]].pre].nl = max(st[st[rk[i]].pre].nl, st[rk[i]].nl); 91 } 92 st[rk[i]].nl = 0; 93 } 94 } 95 for(int i = sam.sz - 1; i >= 1; -- i) { 96 ans = max(ans, st[i].ml); 97 } 98 printf("%d\n", ans); 99 return 0; 100 }
题目8 SPOJ NSUBSTR
题目大意:求一个串长度为i的子串出现次数的最大值。
算法讨论:SAM中每个结点有两个信息维护:len, pre,len是当前点可以接收的子串的最长长度,pre是失配后的后缀连接。除此之外,还有一个我们经常说的right集合。
这个题 就是求Right集合的大小,具体做法是,把当前串建立出SAM,然后在非复制出的节点上跑SAM,就是用原串跑原串的SAM,把跑到的结点的Right集合数++。
接着把SAM拓扑排序,可以用基数排序,按照len从大到小处理,每个点的后缀连接点加上这个点的Right集合的大小,就是这个后缀连接点的Right集合的大小。Right集合的大小
表示当前状态可以接收的所有子符串的出现的次数。然后我们用Right集合去更新答案,最后还要用F[i+1]去更新F[i],可是我并不知道是为什么,而且不这样做的话,交上去也能A。
代码:
1 #include <cstdlib> 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 #include <algorithm> 6 7 using namespace std; 8 9 const int N = 250000 + 5; 10 const int C = 26; 11 12 struct State { 13 int len, pre, next[C]; 14 void clear() { 15 len = pre = 0; 16 memset(next, 0, sizeof next); 17 } 18 }st[N << 1]; 19 20 struct SuffixAutomaton { 21 int sz, last; 22 23 void Init() { 24 for(int i = 0; i < (N << 1); ++ i) { 25 st[i].clear(); 26 } 27 sz = last = 1; 28 st[sz].len = 0; st[sz].pre = -1; 29 sz ++; 30 } 31 32 void add(int c) { 33 int cur = sz ++, p; 34 st[cur].len = st[last].len + 1; 35 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 36 st[p].next[c] = cur; 37 if(p == -1) st[cur].pre = 1; 38 else { 39 int q = st[p].next[c]; 40 if(st[q].len == st[p].len + 1) st[cur].pre = q; 41 else { 42 int cle = sz ++; 43 st[cle].pre = st[q].pre; 44 st[cle].len = st[p].len + 1; 45 for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i]; 46 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 47 st[p].next[c] = cle; 48 st[q].pre = st[cur].pre = cle; 49 } 50 } 51 last = cur; 52 } 53 }sam; 54 55 char str[N]; 56 int len, cs[N], rk[N << 1], rt[N << 1], ans[N]; 57 58 int main() { 59 int p, l; 60 scanf("%s", str); 61 len = strlen(str); 62 sam.Init(); 63 for(int i = 0; i < len; ++ i) sam.add(str[i] - 'a'); 64 for(int i = 1; i < sam.sz; ++ i) cs[st[i].len] ++; 65 for(int i = 1; i <= len; ++ i) cs[i] += cs[i - 1]; 66 for(int i = 1; i < sam.sz; ++ i) rk[cs[st[i].len] --] = i; 67 p = 1; //以下求出了每个结点的right集合大小 68 for(int i = 0; i < len; ++ i) { 69 p = st[p].next[str[i] - 'a']; 70 rt[p] ++; 71 } 72 for(int i = sam.sz - 1; i >= 1; -- i) 73 rt[st[rk[i]].pre] += rt[rk[i]]; 74 for(int i = sam.sz - 1; i >= 1; -- i) { 75 l = st[rk[i]].len; 76 ans[l] = max(ans[l], rt[rk[i]]); 77 } 78 for(int i = 1; i < len; ++ i) 79 ans[i] = max(ans[i], ans[i + 1]); 80 for(int i = 1; i <= len; ++ i) 81 printf("%d\n", ans[i]); 82 return 0; 83 }
题目9 SPOJ SUBLEX
题目大意:把一个串的所有本质不同的子串按照字典序排序后,求第k个串是什么。
算法讨论:对原串建立自动机,然后对SAM拓扑排序,然后我们可以统计出从SAM中的每个结点开始DFS可以得到的子串数目是多少,搞出这个东西,我们就可以在SAM上走启发式的DFS了。。嘿嘿。注意这个东西可不是Right集合。两个东西有着本质的区别。
代码:
1 #include <cstdlib> 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 #include <algorithm> 6 7 using namespace std; 8 9 const int L = 90000 + 5; 10 const int C = 26; 11 12 struct State { 13 int len, pre, next[C]; 14 }st[L << 1]; 15 16 struct SuffixAutomaton { 17 int sz, last; 18 19 void Init() { 20 sz = last = 1; 21 st[sz].pre = -1; 22 st[sz].len = 0; 23 sz ++; 24 } 25 26 void add(int c) { 27 int cur = sz ++, p; 28 st[cur].len = st[last].len + 1; 29 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 30 st[p].next[c] = cur; 31 if(p == -1) st[cur].pre = 1; 32 else { 33 int q = st[p].next[c]; 34 if(st[q].len == st[p].len + 1) st[cur].pre = q; 35 else { 36 int cle = sz ++; 37 st[cle].pre = st[q].pre; 38 st[cle].len = st[p].len + 1; 39 for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i]; 40 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 41 st[p].next[c] = cle; 42 st[cur].pre = st[q].pre = cle; 43 } 44 } 45 last = cur; 46 } 47 }sam; 48 49 char str[L]; 50 int len, rt[L << 1], rk[L << 1], cs[L], num[L]; 51 char que[20]; 52 53 int main() { 54 int p, Q, k; 55 scanf("%s", str); 56 len = strlen(str); 57 sam.Init(); 58 for(int i = 0; i < len; ++ i) 59 sam.add(str[i] - 'a'); 60 for(int i = 1; i < sam.sz; ++ i) cs[st[i].len] ++; 61 for(int i = 1; i <= len; ++ i) cs[i] += cs[i - 1]; 62 for(int i = 1; i < sam.sz; ++ i) rk[cs[st[i].len] --] = i; 63 p = 1; 64 for(int i = 0; i < len; ++ i) { 65 p = st[p].next[str[i] - 'a']; 66 rt[p] ++; 67 } 68 for(int i = sam.sz - 1; i >= 1; -- i) 69 rt[st[rk[i]].pre] += rt[rk[i]]; 70 for(int i = sam.sz - 1; i >= 1; -- i) 71 num[i] = 1; 72 for(int i = sam.sz - 1; i >= 1; -- i) { 73 for(int j = 0; j < C; ++ j) { 74 num[rk[i]] += num[st[rk[i]].next[j]]; 75 } 76 } 77 scanf("%d", &Q); 78 while(Q --) { 79 scanf("%d", &k); 80 p = 1; 81 while(k) { 82 for(int j = 0; j < C; ++ j) { 83 if(num[st[p].next[j]] >= k) { 84 putchar(j + 'a'); 85 k --; 86 p = st[p].next[j]; 87 break; 88 } 89 else { 90 k -= num[st[p].next[j]]; 91 } 92 } 93 } 94 puts(""); 95 } 96 return 0; 97 }
题目10 HDU 4436 string2int
题目大意:给出n个串,求这n个串所有不同子串组成的所有十进制数的和是多少,有前导0的去掉前导0,出现多次的只算一次,答案对2012取模。
算法讨论:多串建立自动机。中间用特殊的字符隔开。然后拓扑排序,从len由小到大处理。这个题自己是又T又Wa,并不知道是为什么。还要再思考思考。
代码:
1 #include <cstdlib> 2 #include <iostream> 3 #include <cstring> 4 #include <algorithm> 5 #include <cstdio> 6 7 using namespace std; 8 9 const int C = 26; 10 const int N = 110000 + 5; 11 typedef long long ll; 12 13 struct State { 14 int pre, len, next[C]; 15 void clear() { 16 pre = len = 0; 17 memset(next, 0, sizeof next); 18 } 19 }st[N << 1]; 20 21 struct SuffixAutomaton { 22 int sz, last; 23 24 void Init() { 25 for(int i = 0; i < sz; ++ i) st[i].clear(); 26 sz = last = 1; 27 st[sz].pre = -1; st[sz].len = 0; 28 sz ++; 29 } 30 31 void add(int c) { 32 int cur = sz ++, p; 33 st[cur].len = st[last].len + 1; 34 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 35 st[p].next[c] = cur; 36 if(p == -1) st[cur].pre = 1; 37 else { 38 int q = st[p].next[c]; 39 if(st[q].len == st[p].len + 1) st[cur].pre = q; 40 else { 41 int cle = sz ++; 42 st[cle].pre = st[q].pre; 43 st[cle].len = st[p].len + 1; 44 for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i]; 45 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 46 st[p].next[c] = cle; 47 st[q].pre = st[cur].pre = cle; 48 } 49 } 50 last = cur; 51 } 52 }sam; 53 54 char str[N]; 55 int lens, n, zz, ans, cr, p, Len; 56 int rk[N << 1], cs[N], sum[N << 1], cnt[N << 1]; 57 58 int main() { 59 while(scanf("%d", &n) != EOF) { 60 memset(sum, 0, sizeof sum); 61 memset(cnt, 0, sizeof cnt); 62 memset(cs, 0, sizeof cs); 63 memset(rk, 0, sizeof rk); 64 sam.Init(); Len = 0; 65 while(n --) { 66 scanf("%s", str); 67 lens = strlen(str); 68 Len += lens; 69 for(int i = 0; i < lens; ++ i) 70 sam.add(str[i] - '0'); 71 sam.add('?' - '0'); 72 Len += 1; 73 } 74 ans = 0; 75 cnt[1] = 1; 76 for(int i = 1; i < sam.sz; ++ i) cs[st[i].len] ++; 77 for(int i = 1; i <= Len; ++ i) cs[i] += cs[i - 1]; 78 for(int i = 1; i < sam.sz; ++ i) rk[cs[st[i].len] --] = i; 79 for(int i = 1; i < sam.sz; ++ i) { 80 p = rk[i]; 81 for(int j = 0; j < 10; ++ j) { 82 if(p == 1 && !j) continue; 83 if(st[p].next[j]) { 84 cr = st[p].next[j]; 85 cnt[cr] = (cnt[cr] + cnt[p]) % 2012; 86 sum[cr] = (sum[cr] + sum[p] * 10 + cnt[p] * j) % 2012; 87 } 88 } 89 ans = (ans + sum[p]) % 2012; 90 } 91 printf("%d\n", ans); 92 } 93 return 0; 94 }
题目11 BZOJ3998 TJOI 弦论
题目大意:把一个串的所有子串按照字典序排序,求第K个。不同的是,如果type = 1,则位置不同的相同的串是不同的串,如果type = 0,则位置不同的相同的串算一个。
算法讨论:对于type = 0则是自动机的长处。对于type = 1的也是自动机的长处,对于type = 0,每次我们让k --就好了,对于type = 1,我们每次让k 减去当前这个right集合的
大小,对于从某个点出发可以得到多少个子串,type = 0,就把num[i]的初始设为1,对于type = 1,就把num[i]的初始设为其right集合的大小 。然后正常计算就可以。
代码:
1 #include <cstdlib> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstdio> 5 #include <cstring> 6 7 using namespace std; 8 9 const int N = 500000 + 5; 10 const int C = 26; 11 12 struct State { 13 int pre, len, next[C]; 14 }st[N << 1]; 15 16 char str[N]; 17 int type, k, len; 18 int cs[N], rk[N << 1], num[N << 1], rt[N << 1]; 19 20 struct SuffixAutomaton { 21 int sz, last; 22 23 void Init() { 24 sz = last = 1; 25 st[sz].pre = -1; st[sz].len = 0; 26 sz ++; 27 } 28 29 void add(int c) { 30 int cur = sz ++, p; 31 st[cur].len = st[last].len + 1; 32 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 33 st[p].next[c] = cur; 34 if(p == -1) st[cur].pre = 1; 35 else { 36 int q = st[p].next[c]; 37 if(st[q].len == st[p].len + 1) st[cur].pre = q; 38 else { 39 int cle = sz ++; 40 st[cle].len = st[p].len + 1; 41 st[cle].pre = st[q].pre; 42 for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i]; 43 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 44 st[p].next[c] = cle; 45 st[q].pre = st[cur].pre = cle; 46 } 47 } 48 last = cur; 49 } 50 }sam; 51 52 int main() { 53 int p; 54 scanf("%s", str); 55 scanf("%d%d", &type, &k); 56 len = strlen(str); 57 sam.Init(); 58 for(int i = 0; i < len; ++ i) 59 sam.add(str[i] - 'a'); 60 for(int i = 1; i < sam.sz; ++ i) cs[st[i].len] ++; 61 for(int i = 1; i <= len; ++ i) cs[i] += cs[i - 1]; 62 for(int i = 1; i < sam.sz; ++ i) rk[cs[st[i].len] --] = i; 63 p = 1;//求Right 64 for(int i = 0; i < len; ++ i) { 65 p = st[p].next[str[i] - 'a']; 66 rt[p] ++; 67 } 68 for(int i = sam.sz - 1; i >= 1; -- i) 69 rt[st[rk[i]].pre] += rt[rk[i]]; 70 //求num 71 if(type) 72 for(int i = sam.sz - 1; i >= 1; -- i) 73 num[rk[i]] = rt[rk[i]]; 74 else 75 for(int i = sam.sz - 1; i >= 1; -- i) 76 num[i] = 1; 77 num[1] = 0; rt[1] = 0; 78 for(int i = sam.sz - 1; i >= 1; -- i) { 79 for(int j = 0; j < C; ++ j) { 80 num[rk[i]] += num[st[rk[i]].next[j]]; 81 } 82 } 83 if(num[1] < k) { puts("-1"); return 0; } 84 p = 1; 85 while(k) { 86 for(int j = 0; j < C; ++ j) { 87 if(num[st[p].next[j]] >= k) { 88 if(type) k -= rt[st[p].next[j]]; 89 else k --; 90 p = st[p].next[j]; 91 putchar(j + 'a'); 92 break; 93 } 94 else { 95 k -= num[st[p].next[j]]; 96 } 97 } 98 } 99 return 0; 100 }
题目12 BZOJ 2555 Subtrings
题目大意:对于一个字符串,有两个操作,一个是向当前串尾部加入一个字符串,另一个操作是询问某个串出现了多少次。强制在线。
算法讨论:首先,求某个串出现了多少次就是把这个串放在自动机上去跑,然后跑到的end结点的Right集合大小就是这个串出现的次数。
但是本题要求还有添加字符,我们不可能每一次都重新计算一下Right集合,所以我们要有一个支持在线修改Right并维护其大小的东西。
由于这东西有很强的树型结构,所以我们采用LInkCutTree来维护它。每次加入一个字符,就把这个字符的Right集合大小置为1.然后有设置pre指针的地方我们就Link,
如果有改变pre指针的地方我们就先Cut再Link,最后把某个串放在上面跑,输出end结点的val值就可以了。
这个题会了两个东西:
1、在Extend的时候求哪些点是有用的点(不是复制出来的点),我原来求Right集合的有用点都是先建立SAM,再把原串放在上面跑,求有用的结点。
LinkCutTree在Link和Cut的时候就可以维护Right大小了。对于第一种方法,我们还要拓扑一下求Right集合大小。
2、用LinkCutTree维护Right集合,有一个好处是Splay中没有pushup。
代码:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cstdio> 5 #include <algorithm> 6 7 using namespace std; 8 9 const int N = 600000 + 5; 10 const int C = 26; 11 12 int sz, last, lens, n, top; 13 char str[N], ss[10]; 14 int fa[N << 1], c[N << 1][2], val[N << 1], add[N << 1], sta[N << 1]; 15 bool rev[N << 1]; 16 17 struct State { 18 int len, pre, next[C]; 19 }st[N << 1]; 20 21 void pushdown(int x) { 22 int l = c[x][0], r = c[x][1]; 23 if(rev[x]) { 24 rev[x] ^= 1; rev[l] ^= 1; rev[r] ^= 1; 25 swap(c[x][0], c[x][1]); 26 } 27 if(add[x]) { 28 val[l] += add[x]; val[r] += add[x]; 29 add[l] += add[x]; add[r] += add[x]; 30 add[x] = 0; 31 } 32 } 33 34 bool isroot(int x) { 35 return c[fa[x]][0] != x && c[fa[x]][1] != x; 36 } 37 38 void rotate(int x) { 39 int y = fa[x], z = fa[y], l, r; 40 l = (c[y][0] == x) ^ 1; r = l ^ 1; 41 if(!isroot(y)) { 42 c[z][(c[z][0] == y) ^ 1] = x; 43 } 44 fa[x] = z; fa[y] = x; fa[c[x][r]] = y; 45 c[y][l] = c[x][r]; c[x][r] = y; 46 } 47 48 void splay(int x) { 49 top = 0; sta[++ top] = x; 50 for(int i = x; !isroot(i); i = fa[i]) 51 sta[++ top] = fa[i]; 52 while(top) pushdown(sta[top --]); 53 while(!isroot(x)) { 54 int y = fa[x], z = fa[y]; 55 if(!isroot(y)) { 56 if((c[z][0] == y) ^ (c[y][0] == x)) rotate(x); 57 else rotate(y); 58 } 59 rotate(x); 60 } 61 } 62 63 void Access(int x) { 64 int t = 0; 65 while(x) { 66 splay(x); c[x][1] = t; 67 t = x; x = fa[x]; 68 } 69 } 70 71 int Findroot(int x) { 72 Access(x); splay(x); 73 while(c[x][0]) x = c[x][0]; 74 return x; 75 } 76 77 void Makeroot(int x) { 78 Access(x); splay(x); rev[x] ^= 1; 79 } 80 81 void Cut(int x) { 82 Access(x); splay(x); 83 val[c[x][0]] -= val[x]; 84 add[c[x][0]] -= val[x]; 85 c[x][0] = fa[c[x][0]] = 0; 86 } 87 88 void Link(int x, int y) { 89 fa[x] = y; 90 Access(y); splay(y); 91 val[y] += val[x]; add[y] += val[x]; 92 } 93 94 void decode(char *s, int mask) { 95 int le = strlen(s); 96 for(int j = 0; j < le; ++ j) { 97 mask = (mask * 131 + j) % le; 98 swap(s[mask], s[j]); 99 } 100 } 101 102 void Init() { 103 sz = last = 1; 104 st[sz].pre = -1; st[sz].len = 0; 105 sz ++; 106 } 107 108 void Extend(int c) { 109 int cur = sz ++, p; 110 st[cur].len = st[last].len + 1; 111 val[cur] = 1; 112 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 113 st[p].next[c] = cur; 114 if(p == -1) st[cur].pre = 1, Link(cur, 1); 115 else { 116 int q = st[p].next[c]; 117 if(st[q].len == st[p].len + 1) st[cur].pre = q, Link(cur, q); 118 else { 119 int cle = sz ++; 120 st[cle].pre = st[q].pre; Link(cle, st[q].pre); 121 st[cle].len = st[p].len + 1; 122 for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i]; 123 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 124 st[p].next[c] = cle; 125 Cut(q); Link(q, cle); Link(cur, cle); 126 st[q].pre = st[cur].pre = cle; 127 } 128 } 129 last = cur; 130 } 131 132 int main() { 133 int mask = 0; 134 scanf("%d", &n); 135 scanf("%s", ss); 136 lens = strlen(ss); 137 Init(); 138 for(int i = 0; i < lens; ++ i) 139 Extend(ss[i] - 'A'); 140 while(n --) { 141 scanf("%s%s", ss, str); 142 lens = strlen(str); 143 if(ss[0] == 'A') { 144 decode(str, mask); 145 for(int i = 0; i < lens; ++ i) 146 Extend(str[i] - 'A'); 147 } 148 else if(ss[0] == 'Q') { 149 decode(str, mask); 150 int p = 1; bool flag = false; 151 for(int i = 0; i < lens; ++ i) { 152 if(!st[p].next[str[i] - 'A']) { 153 flag = true; break; 154 } 155 p = st[p].next[str[i] - 'A']; 156 } 157 if(flag) { 158 puts("0"); continue; 159 } 160 splay(p); 161 printf("%d\n", val[p]); 162 mask ^= val[p]; 163 } 164 } 165 return 0; 166 }
题目13 HDU 4622
题目大意:
给一个串,询问l..r的不同子串个数。
算法讨论:
新加入一个字符,新增的不同子串数为st[cur].len - st[st[cut].pre].len。
对于询问,我们离线操作,左端点相同的我们只维护一个加字符,如果左端点不同,就定期重建自动机。
动态维护不同子串数,SMZ大神自己YY了一个做法,考虑加入一个结点会对多少条从根结点到当前结点的路径造成影响。
代码以SDOI2016的某个水题附上。
代码:
1 #include <cstdlib> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 #include <cstdio> 6 7 using namespace std; 8 9 const int N = 2000 + 5; 10 const int C = 26; 11 12 int Ans = 0; 13 14 struct State { 15 int pre, len, next[C]; 16 void clear() { 17 pre = len = 0; 18 memset(next, 0, sizeof next); 19 } 20 }st[N << 1]; 21 22 struct SuffixAutomaton { 23 int sz, last; 24 25 void Init() { 26 for(int i = 0; i < sz; ++ i) 27 st[i].clear(); 28 sz = last = 1; 29 st[sz].len = 0; st[sz].pre = -1; 30 sz ++; 31 } 32 33 void add(int c) { 34 int cur = sz ++, p; 35 st[cur].len = st[last].len + 1; 36 for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) 37 st[p].next[c] = cur; 38 if(p == -1) st[cur].pre = 1; 39 else { 40 int q = st[p].next[c]; 41 if(st[q].len == st[p].len + 1) st[cur].pre = q; 42 else { 43 int cle = sz ++; 44 st[cle].pre = st[q].pre; 45 st[cle].len = st[p].len + 1; 46 for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i]; 47 for(; p != -1 && st[p].next[c] == q; p = st[p].pre) 48 st[p].next[c] = cle; 49 st[q].pre = st[cur].pre = cle; 50 } 51 } 52 last = cur; 53 Ans = Ans + st[cur].len - st[st[cur].pre].len; 54 } 55 }sam; 56 57 struct Query { 58 int l, r, id; 59 bool operator < (const Query &k) const { 60 if(l == k.l) return r < k.r; 61 return l < k.l; 62 } 63 }qs[10005]; 64 65 int ans[10005], cs[N], rk[N << 1], num[N << 1]; 66 char str[N]; 67 68 int main() { 69 int t, Q; 70 scanf("%d", &t); 71 while(t --) { 72 scanf("%s", str + 1); 73 scanf("%d", &Q); 74 for(int i = 1; i <= Q; ++ i) { 75 scanf("%d%d", &qs[i].l, &qs[i].r); 76 qs[i].id = i; 77 } 78 sort(qs + 1, qs + Q + 1); 79 for(int i = 1; i <= Q; ++ i) { 80 if(qs[i].l == qs[i - 1].l) { 81 for(int j = qs[i - 1].r + 1; j <= qs[i].r; ++ j) 82 sam.add(str[j] - 'a'); 83 } 84 else { 85 sam.Init(); Ans = 0; 86 for(int j = qs[i].l; j <= qs[i].r; ++ j) 87 sam.add(str[j] - 'a'); 88 } 89 ans[qs[i].id] = Ans; 90 } 91 for(int i = 1; i <= Q; ++ i) 92 printf("%d\n", ans[i]); 93 } 94 return 0; 95 }
附SMZ大神的另一个做法:SDOI2016 生成魔咒她的代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <map> 6 #define maxn 200010 7 using namespace std; 8 9 int n; 10 11 int a[maxn]; 12 13 struct Node{ 14 int len, link; 15 map<int, int> nxt; 16 }st[maxn]; 17 18 int root, size, last; 19 void init(){ 20 root = size = last = 0; 21 st[root].len = 0; 22 st[root].link = -1; 23 } 24 25 long long ans; 26 27 long long vis[maxn]; 28 29 void Extend(int c){ 30 int cur = ++ size, p = last; 31 st[cur].len = st[p].len + 1; 32 for(; ~p && st[p].nxt[c] == 0; p = st[p].link) 33 st[p].nxt[c] = cur, vis[cur] += vis[p]; 34 ans += vis[cur]; 35 if(p == -1) 36 st[cur].link = root; 37 else{ 38 int q = st[p].nxt[c]; 39 if(st[q].len == st[p].len + 1) 40 st[cur].link = q; 41 else{ 42 int clone = ++ size; 43 st[clone] = st[q]; 44 st[clone].len = st[p].len + 1; 45 vis[clone] = 0; 46 for(; ~p && st[p].nxt[c] == q; p = st[p].link) 47 st[p].nxt[c] = clone, vis[clone] += vis[p], vis[q] -= vis[p]; 48 st[q].link = st[cur].link = clone; 49 } 50 } 51 last = cur; 52 } 53 54 55 int main(){ 56 freopen("menci_incantation.in", "r", stdin); 57 freopen("menci_incantation.out", "w", stdout); 58 init(); 59 vis[root] = 1; 60 scanf("%d", &n); 61 for(int i = 1; i <= n; i ++){ 62 scanf("%d", &a[i]); 63 Extend(a[i]); 64 printf("%lld\n", ans); 65 } 66 return 0; 67 }
题目14 百度之星2015年复赛 最强密码(提交入口:HDU 5262)
题目大意:
给定一个字符串,求最短的不是其子序列的串的长度和个数。
算法讨论:
序列自动机 + Dp。由于答案只在空节点进行累计,所以我们建立一个空的专门接受没有这个字符出边的节点,
然后在这个节点上进行答案统计就可以,我们从前向后走序列自动机,第一个更新这个节点长度的就是最短串的长度,
后面的节点如果走到这个节点,由于长度都比这个大,所以只会对其进行答案累计。
代码:
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> using namespace std; const int N = 100000 + 5; const int C = 26; const int mod = 1e9 + 7; int len; char str[N]; int last[N], anslen[N], dp[N]; struct Node { int next[C]; }st[N]; int main() { int count = 0, T; scanf("%d", &T); while(T --) { ++ count; printf("Case #%d:\n", count); scanf("%s", str + 1); len = strlen(str + 1); for(int i = 0; i < C; ++ i) last[i] = len + 1; for(int i = len; i >= 1; -- i) { for(int j = 0; j < C; ++ j) st[i].next[j] = last[j]; last[str[i] - 'a'] = i; } for(int i = 0; i < C; ++ i) st[0].next[i] = last[i]; for(int i = 1; i <= len + 1; ++ i) anslen[i] = len + 1; anslen[0] = 0; dp[0] = 1; for(int i = 0; i <= len; ++ i) { for(int j = 0; j < C; ++ j) { int nxt = st[i].next[j]; if(anslen[i] + 1 < anslen[nxt]) { anslen[nxt] = anslen[i] + 1; dp[nxt] = dp[i]; } else if(anslen[i] + 1 == anslen[nxt]) { dp[nxt] += dp[i]; if(dp[nxt] >= mod) dp[nxt] -= mod; } } } printf("%d %d\n", anslen[len + 1], dp[len + 1]); } return 0; }
题目15 HDU 3336
题目大意:
求一个串的所有前缀在这个串中的出现次数和,答案Mod10007
算法讨论:AC自动机
对于每个节点都是一个end结点,所以我们只要在原串的AC自动机上跑原串,像以往那样正常统计出现次数就可以了。
#include <cstdlib> #include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> using namespace std; const int N = 200000 + 5; const int C = 26; const int mod = 10007; typedef long long ll; char str[N]; int n; ll ans = 0; struct Ac { int sz; int c[N][C], fail[N], end[N]; Ac() { sz = 1; } void clear() { sz = 1; memset(c, 0, sizeof c); memset(fail, 0, sizeof fail); memset(end, 0, sizeof end); } int idx(char cc) { return cc - 'a'; } void insert(char *buf) { int cur = 0, len = strlen(buf); for(int i = 0; i < len; ++ i) { int x = idx(buf[i]); if(!c[cur][x]) c[cur][x] = sz ++; cur = c[cur][x]; end[cur] ++; } } void build() { queue <int> q; fail[0] = 0; for(int i = 0; i < C; ++ i) if(!c[0][i]) c[0][i] = 0; else { fail[c[0][i]] = 0; q.push(c[0][i]); } while(!q.empty()) { int x = q.front(); q.pop(); for(int i = 0; i < C; ++ i) { if(!c[x][i]) c[x][i] = c[fail[x]][i]; else { fail[c[x][i]] = c[fail[x]][i]; q.push(c[x][i]); } } } } void query(char *buf) { int cur = 0, len = strlen(buf); for(int i = 0; i < len; ++ i) { int x = idx(buf[i]); cur = c[cur][x]; int tmp = cur; while(tmp) { if(end[tmp]) { ans = ans + end[tmp]; if(ans >= mod) { ans -= mod; } } tmp = fail[tmp]; } } } }ac; int main() { int t; scanf("%d", &t); while(t --) { scanf("%d", &n); scanf("%s", str); ac.insert(str); ac.build(); ac.query(str); printf("%lld\n", (ans + mod) % mod); ans = 0; ac.clear(); } return 0; }
题目16 BZOJ 2780
题目大意:
求一个串在多少个串中出现。
算法讨论:
后缀自动机。对多串建立广义后缀自动机,每个节点都要保存其在哪个串中出现的颜色信息。然后建立出Parent树,求出DFS序。
对于每个询问, 在自动机上先跑一遍,求出其到达的end结点,没有的就直接把答案设为0.
然后将询问全部离线,按照DFS右端点从小到大排序,然后再按照DFS序的顺序处理自动机上的每个结点,
这里用树状数组来维护区间和进行统计答案。有点莫队的思想。计算即可。
代码:
#include <cstdlib> #include <iostream> #include <cstring> #include <algorithm> #include <cstdio> #include <vector> using namespace std; const int N = 100000 + 5; const int C = 128; struct State { int len, pre, next[C]; vector <int> color; }st[N << 1]; struct SuffixAutomaton { int sz, last; void Init() { last = sz = 1; st[sz].pre = -1; st[sz].len = 0; sz ++; } void add(int c, int ccc) { int cur = sz ++, p; st[cur].len = st[last].len + 1; for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre) st[p].next[c] = cur; if(p == -1) st[cur].pre = 1; else { int q = st[p].next[c]; if(st[q].len == st[p].len + 1) st[cur].pre = q; else { int cle = sz ++; st[cle].pre = st[q].pre; st[cle].len = st[p].len + 1; for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i]; for(; p != -1 && st[p].next[c] == q; p = st[p].pre) st[p].next[c] = cle; st[q].pre = st[cur].pre = cle; } } last = cur; st[cur].color.push_back(ccc); } }sam; char str[N]; int n, m, cnt, tim, len; int head[N << 1], in[N << 1], out[N << 1], lastins[N]; int ans[N], seq[N << 1], cc[N << 1]; struct Edge { int from, to, next; }edges[N << 1]; struct Query { int id, l, r; bool operator < (const Query &k) const { return r < k.r; } }Q[N]; void insert(int f, int u) { ++ cnt; edges[cnt].from = f; edges[cnt].to = u; edges[cnt].next = head[f]; head[f] = cnt; } void dfs(int u) { in[u] = ++ tim; seq[tim] = u; for(int i = head[u]; i; i = edges[i].next) dfs(edges[i].to); out[u] = tim; } int LowBit(int x) { return x & (-x); } int query(int x) { int res = 0; for(int i = x; i > 0; i -= LowBit(i)) res += cc[i]; return res; } void update(int x, int v) { for(int i = x; i < sam.sz; i += LowBit(i)) cc[i] += v; } int main() { scanf("%d%d", &n, &m); sam.Init(); for(int i = 1; i <= n; ++ i) { scanf("%s", str); len = strlen(str); for(int j = 0; j < len; ++ j) sam.add(str[j], i); sam.last = 1;//back to the root .... } for(int i = 1; i < sam.sz; ++ i) if(st[i].pre != -1) insert(st[i].pre, i); dfs(1); for(int i = 1; i <= m; ++ i) { scanf("%s", str); len = strlen(str); int p = 1; bool flag = false; for(int j = 0; j < len; ++ j) { if(!st[p].next[(int)str[j]]) { flag = true; break; } else p = st[p].next[(int)str[j]]; } Q[i].id = i; if(flag) { Q[i].l = Q[i].r = -1; } else { Q[i].l = in[p]; Q[i].r = out[p]; } } sort(Q + 1, Q + m + 1); int cur = 1; while(cur <= m && Q[cur].l == -1) ans[Q[cur].id] = 0, cur ++; for(int i = 1; i < sam.sz; ++ i) { for(int j = 0; j < (signed) st[seq[i]].color.size(); ++ j) { int now = st[seq[i]].color[j]; update(i, 1); if(lastins[now]) update(lastins[now], -1); lastins[now] = i; } for(; Q[cur].r == i; ++ cur) ans[Q[cur].id] = query(Q[cur].r) - query(Q[cur].l - 1); } for(int i = 1; i <= m; ++ i) printf("%d\n", ans[i]); return 0; }