SPOJLCS Longest Common Substring
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 wiki,Menci'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;
}
SPOJLCS Longest Common Substring的更多相关文章
- SPOJ-LCS Longest Common Substring 【后缀自动机】
题目分析: 用没出现过的字符搞拼接.搞出right树,找right集合的最小和最大.如果最小和最大分居两侧可以更新答案. 代码: #include<bits/stdc++.h> using ...
- [SPOJ-LCS]Longest Common Substring
题目大意: 求两个字符串的LCS. 思路: 对其中一个字符串构建SAM,然后用另一个字符串在里面匹配,按照SAM的边一直往下走,匹配到的连续的字符数就是公共字串的长度. #include<str ...
- Longest Common Substring II SPOJ - LCS2 (后缀自动机)
Longest Common Substring II \[ Time Limit: 236ms\quad Memory Limit: 1572864 kB \] 题意 给出\(n\)个子串,要求这\ ...
- SPOJ LCS2 - Longest Common Substring II
LCS2 - Longest Common Substring II A string is finite sequence of characters over a non-empty finite ...
- LintCode Longest Common Substring
原题链接在这里:http://www.lintcode.com/en/problem/longest-common-substring/# 题目: Given two strings, find th ...
- Longest Common Substring
Given two strings, find the longest common substring. Return the length of it. Example Given A = &qu ...
- 【SPOJ】1812. Longest Common Substring II(后缀自动机)
http://www.spoj.com/problems/LCS2/ 发现了我原来对sam的理解的一个坑233 本题容易看出就是将所有匹配长度记录在状态上然后取min后再对所有状态取max. 但是不要 ...
- hdu 1403 Longest Common Substring(最长公共子字符串)(后缀数组)
http://acm.hdu.edu.cn/showproblem.php?pid=1403 Longest Common Substring Time Limit: 8000/4000 MS (Ja ...
- 后缀自动机(SAM):SPOJ Longest Common Substring II
Longest Common Substring II Time Limit: 2000ms Memory Limit: 262144KB A string is finite sequence of ...
随机推荐
- What is CRC and how does it works?
What is CRC and how does it works? CRC errors refer to Layer 1 or 2 issues. Two things you should ch ...
- 基于SSM的单点登陆05
springmvc.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns=" ...
- UIScrollView监听静止的数种情况
1.直接通过block -(void)testInBlock { //通过block监听 [UIView animateWithDuration:1.0 animations:^{ self.scro ...
- Spring AOP(1)
- Linux下MySQL 5.6.24的编译安装与部署
MySQL 5.6正式版发布了,相对于5.5版本作出了不少改进,其源码安装配置方式也有所变化,本文根据实际操作,不断尝试,精确还原了安装的具体步骤. 在Linux下安装MySQL前,先确认卸载系统自带 ...
- 线段树 - HDU1166 - 敌兵布阵
2017-07-29 16:41:00 writer:pprp 线段树跟区间操作相关,想要在题目限定的时间内解决问题就需要用线段树这种数据结构来解决: 线段树是一种二叉平衡树 参考书目:张新华的< ...
- 报错org.apache.ibatis.binding.BindingException: Type interface com.atguigu.mybatis.bean.dao.EmployeeMapper is not known to the MapperRegistry.
报错org.apache.ibatis.binding.BindingException: Type interface com.atguigu.mybatis.bean.dao.EmployeeMa ...
- mysql数据库(三):查询的其他用法
一. 查询—IN的用法 语法:select ... from 表名 where 字段 a in (值b, 值c, 值d...) 等价于 select ... from 表名 where 字段a=值b ...
- MYSQL(python)安装记录
捯饬了很长时间,终于安装成功了,特此记录下! MYSQL历史版本下载,一般为绿色版本 地址:http://downloads.mysql.com/archives/community/ MYSQL安装 ...
- 实现QQ抽屉效果
代码: #coding: utf-8 from PyQt4.QtCore import * from PyQt4.QtGui import * import sys QTextCodec.setCod ...