后缀自动机/回文自动机/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 }
3676

 

题目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;
}
2565

 

题目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 }
2160

 

题目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 }
1294

 

题目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 }
UVa719

 

题目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 }
LCS

 

题目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 }
LCS2

 

题目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 }
NSUBSTR

 

题目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 }
SUBLEX

 

题目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 }
HDU 4436

 

题目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 }
BZOj 3998

 

题目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 }
2555

 

题目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 }
4622

附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 }
SDOI 2016

 

题目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;
}

 

posted @ 2016-03-29 17:00  漫步者。!~  阅读(838)  评论(0编辑  收藏  举报