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|)\)。

co int N=5e5;
namespace SAM
{
int tot,last;
int ch[N][26],fail[N]={-1},len[N];
void extend(int k)
{
int cur=++tot;
len[cur]=len[last]+1;
int p=last;
while(~p&&!ch[p][k])
{
ch[p][k]=cur;
p=fail[p];
}
if(p==-1)
fail[cur]=0;
else
{
int q=ch[p][k];
if(len[q]==len[p]+1)
fail[cur]=q;
else
{
int clone=++tot;
std::copy(ch[q],ch[q]+26,ch[clone]);
fail[clone]=fail[q],len[clone]=len[p]+1;
while(~p&&ch[p][k]==q)
{
ch[p][k]=clone;
p=fail[p];
}
fail[cur]=fail[q]=clone;
}
}
last=cur;
}
void ins(char s[],int n)
{
for(int i=0;i<n;++i)
extend(s[i]-'a');
}
void solve(char s[],int n)
{
int ans=0,v=0,l=0;
for(int i=0;i<n;++i)
{
int k=s[i]-'a';
if(ch[v][k])
v=ch[v][k],++l;
else
{
while(~v&&!ch[v][k])
v=fail[v];
if(v==-1)
v=l=0;
else
l=len[v]+1,v=ch[v][k];
}
ans=std::max(ans,l);
}
printf("%d\n",ans);
}
}
char buf[N]; int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
scanf("%s",buf);
SAM::ins(buf,strlen(buf));
scanf("%s",buf);
SAM::solve(buf,strlen(buf));
return 0;
}

【模板】后缀自动机

给定一个只包含小写字母的字符串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|)\)

co int N=2e6;
namespace SAM
{
int tot,last;
int ch[N][26],fail[N],len[N],siz[N];
void init()
{
tot=last=0;
fail[0]=-1,len[0]=0;
}
void extend(int k)
{
int cur=++tot;
len[cur]=len[last]+1,siz[cur]=1;
int p=last;
while(~p&&!ch[p][k])
{
ch[p][k]=cur;
p=fail[p];
}
if(p==-1)
fail[cur]=0;
else
{
int q=ch[p][k];
if(len[p]+1==len[q])
fail[cur]=q;
else
{
int clone=++tot;
std::copy(ch[q],ch[q]+26,ch[clone]);
fail[clone]=fail[q],len[clone]=len[p]+1;
while(~p&&ch[p][k]==q)
{
ch[p][k]=clone;
p=fail[p];
}
fail[q]=fail[cur]=clone;
}
}
last=cur;
}
void ins(char*s,int n)
{
for(int i=0;i<n;++i)
extend(s[i]-'a');
} int nx[N],to[N];
ll ans;
void build()
{
for(int i=1;i<=tot;++i)
nx[i]=to[fail[i]],to[fail[i]]=i;
}
void dfs(int x)
{
for(int i=to[x];i;i=nx[i])
{
dfs(i);
siz[x]+=siz[i];
}
if(siz[x]>1)
ans=std::max(ans,(ll)siz[x]*len[x]);
}
void solve()
{
build();
dfs(0);
printf("%lld\n",ans);
}
}
char buf[N]; int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
SAM::init();
scanf("%s",buf);
SAM::ins(buf,strlen(buf));
SAM::solve();
return 0;
}

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. 《ucore lab5》实验报告

    资源 ucore在线实验指导书 我的ucore实验代码 练习1: 加载应用程序并执行(需要编码) 题目 do_execv函数调用load_icode(位于kern/process/proc.c中) 来 ...

  2. 以环形角度理解php数组索引

    array_slice ( array $array , int $offset [, int $length = NULL [, bool $preserve_keys = false ]] ) : ...

  3. 如何创建etcd双向通信证书

    # 安装证书生成软件 wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -O /usr/bin/cfssl wget https://pkg.cfss ...

  4. Python——raise引发异常

    程序出现错误,会自动引发异常,Python也允许使用raise语句自行引发异常. 一.使用raise引发异常 单独一个raise引发异常,默认引发RuntimeError异常,例: try: prin ...

  5. 【Linux】一步一步学Linux——虚拟机安装和卸载(05)

    目录 00. 目录 01. Workstation Pro 15.0安装简介 02. Windows 主机上安装 Workstation Pro 15.0 03. Linux 主机上安装 Workst ...

  6. MySQL8.0新特性总览

    1.消除了buffer pool mutex (Percona的贡献) 2.数据字典全部采用InnoDB引擎存储,支持DDL原子性.crash safe.metadata管理更完善(可以利用ibd2s ...

  7. closed channel

    func Test_chanel(t *testing.T) { c := make(chan int, 1) go func() { time.Sleep(time.Second * 3) clos ...

  8. PowerBuilder学习笔记之删除和加载PBL文件的方法

    删除PBL目录的方法:直接点删除键删除 加载PBL文件的方法:点Browse按钮选择PBL文件

  9. Vue使用指南(二)

    '''1.指令 ***** 文本指令 属性指令 方法(事件)指令 表单指令 条件指令 循环指令 2.组件 *** 局部组件 全局组件 父子组件间的交互''' 文件指令 <body> < ...

  10. 简单添加自己的驱动程序到Linux内核树中

    背景: 移植4g模块的时候,看到文档中有添加驱动到内核的步骤,于是趁着这个机会,展开有关的学习. 了解更多可以访问:<Kconfig语法简介> Target :hi3531d Linux ...