一、后缀自动机基本概念的理解

1、首先后缀自动机的状态是由子串的endpos来决定的

子串的endpos是指一个子串可以在原字符串的哪些位置进行匹配,

endpos构成的不同集合划分成不同的状态

关于endpos的性质: s1是s2的子串当且仅当endpos(s1)属于endpos(s2),s1不是s2的子串当前仅当endpos(s1)和endpos(s2)的交集为空

2、对于一个用endpos划分的状态,最长的子串为longest(st),最短的为shortest(st),对于任何包含于该状态的子串,都是longest(st)的后缀;同样,对于一个状态中的longest(st)的后缀,如果后缀的长度在longest和shortest之间,那么它就属于这个状态。

如此可以这样理解,一个endpos划分的状态,实际上是longest形成的一系列后缀

3、Link

link是将不同endpos间连接起来的边,实际上是把系列的中断相连

4、Transition function

对于一个状态,首先找到它下一个可能出现的字符有哪些,实际上就是只需要把longest后面添加一下新的字符,然后看这个新的串被哪个状态所包含

那么它的那一系列后缀也被这个状态所包含。

暴力做法 (关于endpos)

hihocoder 1441

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. #include <vector>
  5. #define fi first
  6. #define se second
  7. using namespace std;
  8. typedef pair<int, int> PII;
  9. char str[], temp[];
  10. vector<int> endpos[][];
  11. int n;
  12.  
  13. bool ok(int x, int y, int t){
  14. int len = y-x+;
  15. int s = t-len+;
  16. if(s < ) return false;
  17. for(int i = ; i < len; i++)
  18. if(str[i+x] != str[s+i]) return false;
  19. return true;
  20. }
  21. bool cmp(int x, int y, char* temp){
  22. int len = y-x+;
  23. if(len != strlen(temp)) return false;
  24. for(int i = ; i < len; i++) if(str[i+x] != temp[i]) return false;
  25. return true;
  26. }
  27. void print(char* str, int x, int y){
  28. for(int i = x; i <= y; i++) cout<<str[i];
  29. }
  30.  
  31. int main(){
  32. cin>>str;
  33. int len = strlen(str);
  34. for(int i = ; i < len; i++){
  35. for(int j = i; j < len; j++)
  36. for(int k = ; k < len; k++)
  37. if(ok(i, j, k)) endpos[i][j].push_back(k+);
  38. }
  39. cin>>n;
  40. while(n--){
  41. cin>>temp;
  42. PII s;
  43. for(int i = ; i < len; i++)
  44. for(int j = i; j < len; j++)
  45. if(cmp(i, j, temp))
  46. { s = {i, j}; break; }
  47. auto x = endpos[s.fi][s.se];
  48. int longest = , shortest = 1e9;
  49. PII ll, ss;
  50. for(int i = ; i < len; i++)
  51. for(int j = i; j < len; j++){
  52. auto y = endpos[i][j];
  53. if(x.size() != y.size()) continue;
  54. int fail = ;
  55. for(int k = ; k < x.size(); k++) if(x[k] != y[k]) fail = ;
  56. if(fail) continue;
  57. if(longest < j-i+) { longest = j-i+; ll = {i, j}; }
  58. if(shortest > j-i+) { shortest = j-i+; ss = {i, j}; }
  59. }
  60. print(str, ss.fi, ss.se); cout<<" ";
  61. print(str, ll.fi, ll.se); cout<<" ";
  62. for(auto tt : x) cout<<tt<<" "; cout<<endl;
  63. }
  64. }

二、算法部分

hihocoder上讲的很详细

但是只是给出了实现的做法,算法的正确性并没有给出详尽的证明,以后看情况补充吧(挖坑)

算法分成三种情况。运用增量法,取上一次的状态

顺着它的link走,可以得到它的所有后缀,所以就是所有后缀加上这次新的字符

首先建立一个新的状态z代表S[1...i+1],maxlen显然是i+1

①如果link-path上都没有这个新的字符,就全部直接连新的状态,link[z] = s,更新minlen

②如果link-path上有一个状态x,它加上新的字符可以转移到另一个状态y,做如下处理

1、如果maxlen[x]+1 = maxlen[y],那么说明实际上x是z的longest的一系列后缀,只不过不在同一状态中,所以直接link[z] = x即可,更新minlen

2、如果maxlen[x]+1 < maxlen[y],那么我们就把y结点分成两部分,一部分p是maxlen[y] <= maxlen[x]+1,这部分实际上和1是一样的。另一部分q是maxlen[y] > maxlen[x] + 1

实际上x并不能转移到q,所以q留在原地,新建一个结点代表p,让x连向p,然后link[p] = x,  link[q] = link[z] = p。

对于剩下的link-path上的状态,如果它们连向y的话,就重新连向p。最后更新一下p的minlen

三、题目练习

hihocoder 1445

题目大意:给出一个串,求出不重复子串的个数

答案就是每个状态的longest减去shortest,可以保证没有重复的情况出现

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. using namespace std;
  5. int n = , len, st;
  6. const int maxL = 1e6 + ;
  7. int maxlen[*maxL], minlen[*maxL], trans[*maxL][], slink[*maxL];
  8. int new_state(int _maxlen, int _minlen, int *_trans, int _slink){
  9. maxlen[n] = _maxlen;
  10. minlen[n] = _minlen;
  11. for(int i = ; i < ; i++){
  12. if(_trans == NULL)
  13. trans[n][i] = -;
  14. else
  15. trans[n][i] = _trans[i];
  16. }
  17. slink[n] = _slink;
  18. return n++;
  19. }
  20.  
  21. int add_char(char ch, int u){
  22. int c = ch - 'a';
  23. int z = new_state(maxlen[u]+, -, NULL, -);
  24. int v = u;
  25. while(v != - && trans[v][c] == -){
  26. trans[v][c] = z;
  27. v = slink[v];
  28. }
  29. if(v == -){
  30. minlen[z] = ;
  31. slink[z] = ;
  32. return z;
  33. }
  34. int x = trans[v][c];
  35. if(maxlen[v] + == maxlen[x]){
  36. minlen[z] = maxlen[x] + ;
  37. slink[z] = x;
  38. return z;
  39. }
  40. int y = new_state(maxlen[v] + , -, trans[x], slink[x]);
  41. slink[y] = slink[x];
  42. minlen[x] = maxlen[y] + ;
  43. slink[x] = y;
  44. minlen[z] = maxlen[y] + ;
  45. slink[z] = y;
  46. int w = v;
  47. while(w != - && trans[w][c] == x){
  48. trans[w][c] = y;
  49. w = slink[w];
  50. }
  51. minlen[y] = maxlen[slink[y]] + ;
  52. return z;
  53. }
  54.  
  55. char str[maxL];
  56. int main()
  57. {
  58. cin>>str;
  59. st = new_state(, , NULL, -);
  60. int len = strlen(str);
  61. for(int i = ; i < len; i++) {
  62. st = add_char(str[i], st);
  63. }
  64. long long ans = ;
  65. for(int i = ; i < n; i++) ans += (maxlen[i] - minlen[i] + );
  66. cout<<ans<<endl;
  67. return ;
  68. }

hihocoder 1449

给定一个串,要求求出长度为k的子串中重复最多的串出现的次数

问题实际上转换成了求endpos的大小

在建立完后缀自动机后,我们用link可以连接成一棵树

对于父结点的孩子若干个孩子,实际上我们有

endpos[fa] >= sigma(endpos[son])

一般情况下是等于的,但是如果这一点的状态恰好表示了一个前缀,那么就要加1

而前缀的那些点其实是加入的那些,所以加入的过程中标记一下即可

最后求答案的时候,对于一个状态我们实际上要用endpos[x]更新minlen[x] ~ maxlen[x]

但是实际上我们只需要更新maxlen,原因是答案一定是随长度递增的

所以最后做一个这样的处理 ans[i] = max(ans[i], ans[i+1]就可以了

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. #include <queue>
  5. using namespace std;
  6. int n = , len, st;
  7. const int maxL = 1e6 + ;
  8. int maxlen[*maxL], minlen[*maxL], trans[*maxL][], slink[*maxL], lab[*maxL], ans[*maxL], son[*maxL], endpos[*maxL];
  9. int new_state(int _maxlen, int _minlen, int *_trans, int _slink){
  10. maxlen[n] = _maxlen;
  11. minlen[n] = _minlen;
  12. for(int i = ; i < ; i++){
  13. if(_trans == NULL)
  14. trans[n][i] = -;
  15. else
  16. trans[n][i] = _trans[i];
  17. }
  18. slink[n] = _slink;
  19. return n++;
  20. }
  21.  
  22. int add_char(char ch, int u){
  23. int c = ch - 'a';
  24. int z = new_state(maxlen[u]+, -, NULL, -); lab[z] = ;
  25. int v = u;
  26. while(v != - && trans[v][c] == -){
  27. trans[v][c] = z;
  28. v = slink[v];
  29. }
  30. if(v == -){
  31. minlen[z] = ;
  32. slink[z] = ;
  33. return z;
  34. }
  35. int x = trans[v][c];
  36. if(maxlen[v] + == maxlen[x]){
  37. minlen[z] = maxlen[x] + ;
  38. slink[z] = x;
  39. return z;
  40. }
  41. int y = new_state(maxlen[v] + , -, trans[x], slink[x]);
  42. slink[y] = slink[x];
  43. minlen[x] = maxlen[y] + ;
  44. slink[x] = y;
  45. minlen[z] = maxlen[y] + ;
  46. slink[z] = y;
  47. int w = v;
  48. while(w != - && trans[w][c] == x){
  49. trans[w][c] = y;
  50. w = slink[w];
  51. }
  52. minlen[y] = maxlen[slink[y]] + ;
  53. return z;
  54. }
  55.  
  56. char str[maxL];
  57. int main()
  58. {
  59. cin>>str;
  60. st = new_state(, , NULL, -);
  61. int len = strlen(str);
  62. for(int i = ; i < len; i++) {
  63. st = add_char(str[i], st);
  64. }
  65. for(int i = ; i <= n; i++) son[slink[i]]++;
  66. queue<int> Q;
  67. for(int i = ; i <= n; i++) if(son[i] == ) Q.push(i), endpos[i] = ;
  68. while(!Q.empty()){
  69. int x = Q.front(); Q.pop();
  70. if(x == ) continue;
  71. int y = slink[x];
  72. son[y]--; endpos[y] += endpos[x];
  73. if(son[y] == ){
  74. if(lab[y]) endpos[y]++;
  75. Q.push(y);
  76. }
  77. }
  78. for(int i = ; i <= n; i++) ans[maxlen[i]] = max(ans[maxlen[i]], endpos[i]);
  79. for(int i = len-; i >= ; i--) ans[i] = max(ans[i], ans[i+]);
  80. for(int i = ; i <= len; i++) cout<<ans[i]<<endl;
  81. return ;
  82. }

hihocoder 后缀自动机专题的更多相关文章

  1. 后缀自动机专题(hihocoder)

    传送门 #1445 : 后缀自动机二·重复旋律5 题意: 给出字符串\(s\),询问字符串\(s\)中有多少不同的子串. 思路: 考虑对\(s\)建后缀自动机,那么\(\sum (len[i]-len ...

  2. hihoCoder 后缀自动机三·重复旋律6

    后缀自动机三·重复旋律6 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi ...

  3. hihocoder 后缀自动机五·重复旋律8 求循环同构串出现的次数

    描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 小Hi发现旋律可以循环,每次把一段旋律里面最前面一个音换到最后面就成为了原旋律的“循环相似旋律”,还可以 ...

  4. hihocoder 后缀自动机二·重复旋律5

    求不同子串个数 裸的后缀自动机 #include<cstring> #include<cmath> #include<iostream> #include<a ...

  5. hihocoder 后缀自动机四·重复旋律6

    题目 对于\(k\in[1,n]\)求出长度为\(k\)的子串出现次数最多的出现了多少次 我直到现在才理解后缀自动机上的子树和是什么意思 非常显然的一点是 \[endpos(link(u))⊇endp ...

  6. hihocoder 后缀自动机四·重复旋律7

    题目 在\(DAG\)上跑一个\(dp\)就好了 设\(ans_i\)表示到了\(SAM\)的\(i\)位置上所有的子串形成的数的和,之后我们顺便记录一个方案数\(d_i\) 之后我们直接转移就好了 ...

  7. 【hihocoder#1413】Rikka with String 后缀自动机 + 差分

    搞了一上午+接近一下午这个题,然后被屠了个稀烂,默默仰慕一晚上学会SAM的以及半天4道SAM的hxy大爷. 题目链接:http://hihocoder.com/problemset/problem/1 ...

  8. 【hihoCoder 1466】后缀自动机六·重复旋律9

    http://hihocoder.com/problemset/problem/1466 建出A串和B串的两个后缀自动机 对后缀自动机的每个状态求出sg值. 求出B串的\(sum(x)\),表示B有多 ...

  9. hihoCoder #1465 : 后缀自动机五·重复旋律8

    http://hihocoder.com/problemset/problem/1465 求S的循环同构串在T中的出现次数 将串S变成SS 枚举SS的每个位置i,求出以i结尾的SS的子串 与 T的最长 ...

随机推荐

  1. DevOps - 版本控制 - GitHub

    README Badges 徽章 Shields.io: Quality metadata badges for open source projects  徽章 官网:https://shields ...

  2. web开发学习路线

    第一阶段: HTML+CSS: HTML进阶.CSS进阶.div+css布局.HTML+css整站开发. JavaScript基础: Js基础教程.js内置对象常用方法.常见DOM树操作大全.ECMA ...

  3. webpack 4.14配置详解

    1.安装nodejs 官网下载nodejs,安装时可能会爆 2503错误,解决办法是:使用管理员命令执行安装文件.cmd ->命令提示符(管理员)-> 输入: msiexec /packa ...

  4. python面向对象-多继承区别

    #!/usr/local/bin/python3 # -*- coding:utf-8 -*- ''' 构造方法继承策略: 在python2中,经典类是按照深度优先继承构造方法的:新式类是按照广度优先 ...

  5. python之三元运算

    三元运算(三目运算):用于较简单的判断(if   else). if True: return s = "aaaa" else: return s = "bbbb&quo ...

  6. python系列7进程线程和协程

    目录 进程 线程 协程  上下文切换 前言:线程和进程的关系图 由下图可知,在每个应用程序执行的过程中,都会去产生一个主进程和主线程来完成工作,当我们需要并发的执行的时候,就会通过主进程去生成一系列的 ...

  7. 密码发生器 南阳acm519

    密码发生器 时间限制:1000 ms  |  内存限制:65535 KB 难度:2   描述 在对银行账户等重要权限设置密码的时候,我们常常遇到这样的烦恼:如果为了好记用生日吧,容易被破解,不安全:如 ...

  8. PHP.45-TP框架商城应用实例-后台20-权限管理-RBAC表构造与代码生成

    权限管理 三张主表{p39_privilege(权限).p39_role(角色).p39_admin(管理)} 两张中间表{p39_role_pri(角色-权限).p39_admin_role(管理- ...

  9. Android开发——Android手机屏幕适配方案总结

    )密度无关像素,单位为dp,是Android特有的单位 Android开发时通常使用dp而不是px单位设置图片大小,因为它可以保证在不同屏幕像素密度的设备上显示相同的效果. /** * dp与px的转 ...

  10. A problem occurred evaluating project ':'. > ASCII

    项目编译出错: 错误信息如下: FAILURE: Build failed with an exception. * Where: Build file 'F:\git\i***\build.grad ...