Longest Common Substring

给两个串A和B,求这两个串的最长公共子串。

no more than 250000

分析

参照OI wiki

给定两个字符串 S 和 T ,求出最长公共子串,公共子串定义为在 S 和 T 中 都作为子串出现过的字符串 X 。

我们为字符串 S 构造后缀自动机。

我们现在处理字符串 T ,对于每一个前缀都在 S 中寻找这个前缀的最长后缀。换句话 说,对于每个字符串 T 中的位置,我们想要找到这个位置结束的 S 和 T 的最长公 共子串的长度。

为了达到这一目的,我们使用两个变量,当前状态 v 和 当前长度 l 。这两 个变量描述当前匹配的部分:它的长度和它们对应的状态。

一开始 v=t_0且 l=0 ,即,匹配为空串。

现在我们来描述如何添加一个字符 T[i] 并为其重新计算答案:

如果存在一个从 v 到字符 T[i] 的转移,我们只需要转移并让 l 自增一。

如果不存在这样的转移,我们需要缩短当前匹配的部分,这意味着我们需要按照以下后 缀链接进行转移:

v=link(v)

与此同时,需要缩短当前长度。显然我们需要将 l 赋值为 len(v) ,因为经过这个后缀链接后我们到达的状态所对应的最长字符串是一个子串。

如果仍然没有使用这一字符的转移,我们继续重复经过后缀链接并减小 l ,直到我们 找到一个转移或到达虚拟状态 -1 (这意味着字符 T[i] 根本没有在 S 中出现过, 所以我们设置 v=l=0 )。

问题的答案就是所有 l 的最大值。

这一部分的时间复杂度为 O(length(T)) ,因为每次移动我们要么可以使 l 增加一, 要么可以在后缀链接间移动几次,每次都减小 l 的值。

时间复杂度\(O(|S| + |T|)\)。

  1. co int N=5e5;
  2. namespace SAM
  3. {
  4. int tot,last;
  5. int ch[N][26],fail[N]={-1},len[N];
  6. void extend(int k)
  7. {
  8. int cur=++tot;
  9. len[cur]=len[last]+1;
  10. int p=last;
  11. while(~p&&!ch[p][k])
  12. {
  13. ch[p][k]=cur;
  14. p=fail[p];
  15. }
  16. if(p==-1)
  17. fail[cur]=0;
  18. else
  19. {
  20. int q=ch[p][k];
  21. if(len[q]==len[p]+1)
  22. fail[cur]=q;
  23. else
  24. {
  25. int clone=++tot;
  26. std::copy(ch[q],ch[q]+26,ch[clone]);
  27. fail[clone]=fail[q],len[clone]=len[p]+1;
  28. while(~p&&ch[p][k]==q)
  29. {
  30. ch[p][k]=clone;
  31. p=fail[p];
  32. }
  33. fail[cur]=fail[q]=clone;
  34. }
  35. }
  36. last=cur;
  37. }
  38. void ins(char s[],int n)
  39. {
  40. for(int i=0;i<n;++i)
  41. extend(s[i]-'a');
  42. }
  43. void solve(char s[],int n)
  44. {
  45. int ans=0,v=0,l=0;
  46. for(int i=0;i<n;++i)
  47. {
  48. int k=s[i]-'a';
  49. if(ch[v][k])
  50. v=ch[v][k],++l;
  51. else
  52. {
  53. while(~v&&!ch[v][k])
  54. v=fail[v];
  55. if(v==-1)
  56. v=l=0;
  57. else
  58. l=len[v]+1,v=ch[v][k];
  59. }
  60. ans=std::max(ans,l);
  61. }
  62. printf("%d\n",ans);
  63. }
  64. }
  65. char buf[N];
  66. int main()
  67. {
  68. // freopen(".in","r",stdin);
  69. // freopen(".out","w",stdout);
  70. scanf("%s",buf);
  71. SAM::ins(buf,strlen(buf));
  72. scanf("%s",buf);
  73. SAM::solve(buf,strlen(buf));
  74. return 0;
  75. }

【模板】后缀自动机

给定一个只包含小写字母的字符串S,

请你求出 S 的所有出现次数不为 1 的子串的出现次数乘上该子串长度的最大值。

对于100%的数据,|S|<=106

分析

学习资料:OI wikiMenci's Blog,张天杨《后缀自动机及其应用》。

证明后缀自动机的状态数和转移数是线性的:

不同的状态的Right集只能是不相交或者真包含关系。

这种性质很容易让人想到树对不对,按照真包含关系做成树的话,大于一个元素的集合必定有大于等于两个儿子(保证了不会出现无用的链),只有一个元素的n个集合就是叶节点,所以总节点数小于等于\(2n-1\)。这样就说明状态数是线性的了。还差一点点,那就是转移数也是线性的。下面就来证明:

取自动机的一个有向的生成树,使得初始状态出发可以到达所有状态。对于一条不在树上的边,我们可以在树上从初始状态走到它的起点,然后经过它并走到终态,走的这条路径对应的一定是原串的一个后缀(初态→终态,后缀自动机)。所以每条非树边一定可以对应某些后缀,而不同的两条非树边对应的后缀不可能有重复(若重复,跑该后缀的时候状态转移不唯一),所以非树边数小于等于\(n-1\)。算上生成树上的边,我们得到:转移数小于等于\((2n-1-1)+(n-1)=3n-3\)。

我们令叶子节点的size=1.暴力建出parent树然后dfs,求出每个节点的right集合size,然后求len×size的最大值就行了。

时间复杂度:\(O(|S|)\)

  1. co int N=2e6;
  2. namespace SAM
  3. {
  4. int tot,last;
  5. int ch[N][26],fail[N],len[N],siz[N];
  6. void init()
  7. {
  8. tot=last=0;
  9. fail[0]=-1,len[0]=0;
  10. }
  11. void extend(int k)
  12. {
  13. int cur=++tot;
  14. len[cur]=len[last]+1,siz[cur]=1;
  15. int p=last;
  16. while(~p&&!ch[p][k])
  17. {
  18. ch[p][k]=cur;
  19. p=fail[p];
  20. }
  21. if(p==-1)
  22. fail[cur]=0;
  23. else
  24. {
  25. int q=ch[p][k];
  26. if(len[p]+1==len[q])
  27. fail[cur]=q;
  28. else
  29. {
  30. int clone=++tot;
  31. std::copy(ch[q],ch[q]+26,ch[clone]);
  32. fail[clone]=fail[q],len[clone]=len[p]+1;
  33. while(~p&&ch[p][k]==q)
  34. {
  35. ch[p][k]=clone;
  36. p=fail[p];
  37. }
  38. fail[q]=fail[cur]=clone;
  39. }
  40. }
  41. last=cur;
  42. }
  43. void ins(char*s,int n)
  44. {
  45. for(int i=0;i<n;++i)
  46. extend(s[i]-'a');
  47. }
  48. int nx[N],to[N];
  49. ll ans;
  50. void build()
  51. {
  52. for(int i=1;i<=tot;++i)
  53. nx[i]=to[fail[i]],to[fail[i]]=i;
  54. }
  55. void dfs(int x)
  56. {
  57. for(int i=to[x];i;i=nx[i])
  58. {
  59. dfs(i);
  60. siz[x]+=siz[i];
  61. }
  62. if(siz[x]>1)
  63. ans=std::max(ans,(ll)siz[x]*len[x]);
  64. }
  65. void solve()
  66. {
  67. build();
  68. dfs(0);
  69. printf("%lld\n",ans);
  70. }
  71. }
  72. char buf[N];
  73. int main()
  74. {
  75. // freopen(".in","r",stdin);
  76. // freopen(".out","w",stdout);
  77. SAM::init();
  78. scanf("%s",buf);
  79. SAM::ins(buf,strlen(buf));
  80. SAM::solve();
  81. return 0;
  82. }

SPOJ LCS Longest Common Substring 和 LG3804 【模板】后缀自动机的更多相关文章

  1. 后缀自动机(SAM) :SPOJ LCS - Longest Common Substring

    LCS - Longest Common Substring no tags  A string is finite sequence of characters over a non-empty f ...

  2. LCS2 - Longest Common Substring II(spoj1812)(sam(后缀自动机)+多串LCS)

    A string is finite sequence of characters over a non-empty finite set \(\sum\). In this problem, \(\ ...

  3. SPOJ LCS - Longest Common Substring 字符串 SAM

    原文链接http://www.cnblogs.com/zhouzhendong/p/8982392.html 题目传送门 - SPOJ LCS 题意 求两个字符串的最长公共连续子串长度. 字符串长$\ ...

  4. SPOJ LCS Longest Common Substring(后缀自动机)题解

    题意: 求两个串的最大\(LCS\). 思路: 把第一个串建后缀自动机,第二个串跑后缀自动机,如果一个节点失配了,那么往父节点跑,期间更新答案即可. 代码: #include<set> # ...

  5. spoj 1811 LCS - Longest Common Substring (后缀自己主动机)

    spoj 1811 LCS - Longest Common Substring 题意: 给出两个串S, T, 求最长公共子串. 限制: |S|, |T| <= 1e5 思路: dp O(n^2 ...

  6. spoj1811 LCS - Longest Common Substring

    地址:http://www.spoj.com/problems/LCS/ 题面: LCS - Longest Common Substring no tags  A string is finite ...

  7. 【SPOJ】Longest Common Substring II (后缀自动机)

    [SPOJ]Longest Common Substring II (后缀自动机) 题面 Vjudge 题意:求若干个串的最长公共子串 题解 对于某一个串构建\(SAM\) 每个串依次进行匹配 同时记 ...

  8. 【SPOJ】Longest Common Substring(后缀自动机)

    [SPOJ]Longest Common Substring(后缀自动机) 题面 Vjudge 题意:求两个串的最长公共子串 题解 \(SA\)的做法很简单 不再赘述 对于一个串构建\(SAM\) 另 ...

  9. 【SP1811】LCS - Longest Common Substring

    [SP1811]LCS - Longest Common Substring 题面 洛谷 题解 建好后缀自动机后从初始状态沿着现在的边匹配, 如果失配则跳它的后缀链接,因为你跳后缀链接到达的\(End ...

随机推荐

  1. pandas对时间列分组求diff遇到的问题

    例子: df = pd.DataFrame() df['A'] = [1, 1, 2] df['B'] = [datetime.date(2018, 1, 2), datetime.date(2018 ...

  2. 与TypeScript的一场美丽邂逅

    TypeScript(一)前言:当你点开这篇文章时,我相信你已经在很多地方都已经听说过或者见过TypeScript了.但是可能对TypeScript依然有很多问号:TypeScript到底是什么?为什 ...

  3. 根据SNP的位置从基因组提取上下游序列

      代码如下: #!/usr/bin/perl -w use strict; die "perl $0 <vcf> <genome>" if(@ARGV = ...

  4. 软件素材---linux C语言:向文件末尾进行追加数据

    void AppendDataToFile(char* filePath, char* msg) { // 以附加方式打开可读/写的文件, 如果没有此文件则会进行创建,然后以附加方式打开可读/写的文件 ...

  5. npm use local module

    情况是这样的, 我一个Angular的项目和一个微信小程序要共用逻辑, 于是我就把它剥离出来一个Node类库, Angular倒是可以使用Reference去引用, 但是使用uniapp创建的微信小程 ...

  6. 用pytorch1.0搭建简单的神经网络:进行回归分析

    搭建简单的神经网络:进行回归分析 import torch import torch.nn.functional as F # 包含激励函数 import matplotlib.pyplot as p ...

  7. 1.IO的演进

      1.Java IO 演进之路 本文围绕着一下几个问题 1.Java 中 BIO.NIO.AIO 之间的区别及应用场景. 2.阻塞(Block)与非阻塞(Non-Block)区别. 3.同步(Syn ...

  8. 函数的第一类对象,f格式化,迭代器以及递归

    函数名的第一类对象及使用,f格式化以及迭代器 1.函数的第一类对象 第一类对象 --特殊点 1.可以当作值被赋值给变量 def func(): print(1) a = func a() 2.可以当作 ...

  9. 调用WebService时加入身份验证,以拒绝未授权的访问

    众所周知,WebService是为企业需求提供的在线应用服务,其他公司或应用软件能够通过Internet来访问并使用这项在线服务.但在有些时候的某些应用服务不希望被未授权访问,那么此时我们可以一下几种 ...

  10. 使用dockers安装MySQL

    事前准备 关闭selinux setenforce 0 vim /etc/sysconfig/selinux SELINUX=disabled # 若不关闭,使用docker启动mysql5.7镜像容 ...