http://www.spoj.com/problems/LCS/

题目:求两个串的最长公共子串

参考:https://www.cnblogs.com/autoint/p/10345276.html

分析:

给定两个字符串 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|)

 
#include <bits/stdc++.h>
#define LL long long
#define P pair<int, int>
#define lowbit(x) (x & -x)
#define mem(a, b) memset(a, b, sizeof(a))
#define rep(i, a, n) for (int i = a; i <= n; ++i)
const int maxn =;
#define mid ((l + r) >> 1)
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
string str1,str2;
struct SAM{ int trans[maxn<<][], slink[maxn<<], maxlen[maxn<<];
int last, now, root, len;
inline void newnode (int v) {
maxlen[++now] = v;
} inline void extend(int c) {
newnode(maxlen[last] + );
int p = last, np = now;
// 更新trans
while (p && !trans[p][c]) {
trans[p][c] = np;
p = slink[p];
}
if (!p) slink[np] = root;
else {
int q = trans[p][c];
if (maxlen[p] + != maxlen[q]) {
// 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q]
newnode(maxlen[p] + );
int nq = now;
memcpy(trans[nq], trans[q], sizeof(trans[q]));
slink[nq] = slink[q];
slink[q] = slink[np] = nq;
while (p!=- && trans[p][c] == q) {
trans[p][c] = nq;
p = slink[p];
}
}else slink[np] = q;
}
last = np;
// 初始状态为可接受状态 }
inline void init()
{
memset(trans,,sizeof(trans));
memset(slink,,sizeof(slink));
memset(maxlen,,sizeof(maxlen));
root = last=now=;
}
inline void build(string s)
{
len=s.size();
for(int i= ; i<len ; i++)
extend(s[i]-'a');
}
inline int work(string s)
{
int Len=s.size();
int t1=;
int ret=;
int now=root;
for(int i= ; i<Len ; i++)
{
int ind=s[i]-'a';
while(now!= && trans[now][ind]==)
{
now=slink[now];
if(now!=) t1=maxlen[now];
}
if(now==)
{
now=root ; t1=;
}
else
{
now=trans[now][ind];
t1++;
ret=max(ret,t1);
}
}
return ret;
} }sam; int main()
{
sam.init();
cin>>str1>>str2;
sam.build(str1);
printf("%d\n",sam.work(str2));
}

时间复杂的O(n)

 
求n个串的最长公共字串

本题容易看出就是将所有匹配长度记录在状态上然后取min后再对所有状态取max。

但是不要忘记了一点:更新parent树的祖先。

为什么呢?首先如果子树被匹配过了,那么长度一定大于任意祖先匹配的长度(甚至有些祖先匹配长度为0!为什么呢,因为我们在匹配的过程中,只是找到一个子串,可能还遗漏了祖先没有匹配到,这样导致了祖先的记录值为0,那么在对对应状态取min的时候会取到0,这样就wa了。而且注意,如果匹配到了当前节点,那么祖先们一定都可以赋值为祖先的length!因为当前节点的length大于任意祖先。(

比如数据

acbbc
bc
ac

答案应该是1没错吧。如果没有更新祖先,那么答案会成0。

这个多想想就行了。

所以以后记住:对任意多串匹配时,凡是对同一个状态取值时,要注意当前状态的子树是否比当前状态记录的值优。

#include <bits/stdc++.h>
#define LL long long
#define P pair<int, int>
#define lowbit(x) (x & -x)
#define mem(a, b) memset(a, b, sizeof(a))
#define rep(i, a, n) for (int i = a; i <= n; ++i)
const int maxn = ;
#define mid ((l + r) >> 1)
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
// __int128 read() { __int128 x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f;}
// void print(__int128 x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) print(x / 10); putchar(x % 10 + '0');}
const LL mod = 1e9 + ;
int len;
struct SAM{ int trans[maxn<<][], slink[maxn<<], maxlen[maxn<<];
// 用来求endpos
int indegree[maxn<<], endpos[maxn<<], rank[maxn<<], ans[maxn<<];
// 计算所有子串的和(0-9表示)
LL sum[maxn<<];
int mx[maxn],mn[maxn];
int last, now, root; inline void newnode (int v) {
maxlen[++now] = v;
mn[now]=v;
mem(trans[now],);
} inline void extend(int c) {
newnode(maxlen[last] + );
int p = last, np = now; // 更新trans
while (p && !trans[p][c]) {
trans[p][c] = np;
p = slink[p];
}
if (!p) slink[np] = root;
else {
int q = trans[p][c];
if (maxlen[p] + != maxlen[q]) {
// 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q]
newnode(maxlen[p] + );
int nq = now; memcpy(trans[nq], trans[q], sizeof(trans[q]));
slink[nq] = slink[q];
slink[q] = slink[np] = nq;
while (p && trans[p][c] == q) {
trans[p][c] = nq;
p = slink[p];
}
}else slink[np] = q;
}
last = np;
// 初始状态为可接受状态
endpos[np] = ;
} inline void init()
{
root = last = now = ;
slink[root]=;
mem(trans[root],);
mem(mx,);
} inline void getEndpos() {
// topsort
for (int i = ; i <= now; ++i) indegree[ maxlen[i] ]++; // 统计相同度数的节点的个数
for (int i = ; i <= now; ++i) indegree[i] += indegree[i-]; // 统计度数小于等于 i 的节点的总数
for (int i = ; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; // 为每个节点编号,节点度数越大编号越靠后 }
inline void woke(char *s)
{
int x=root;
int t1=;
int len=strlen(s);
for(int i= ; i<len ; i++)
{
int ind=s[i]-'a';
if(trans[x][ind])
{
x=trans[x][ind];
t1++;
mx[x]=max(mx[x] ,t1);
}
else
{
while(x!= && trans[x][ind]==)
{
x=slink[x];
}
if(x==)
{
x=root;
t1=; }
else
{
t1=maxlen[x]+;
x=trans[x][ind];
mx[x]=max(mx[x],t1);
}
}
}
for(int i=now ; i>= ; i--)
{
int x=rank[i];
mn[x] = min(mn[x] , mx[x]);
if(slink[x]) mx[slink[x]] = max(mx[slink[x]] , mx[x]);
mx[x]=;
}
} }sam;
char s[maxn];
int main()
{ string T;cin>>T;
sam.init();
len=T.size();
for(int i= ; i<len ; i++)
sam.extend(T[i]-'a');
sam.getEndpos();
while(~scanf("%s",s))
{
sam.woke(s);
}
int ans=;
// cout<<sam.now<<endl;
for(int i= ; i<=sam.now ; i++)
{
ans=max(ans,sam.mn[i]);
}
printf("%d\n",ans); //- sam.all();
}
 

SPOJ 1811 Longest Common Substring(求两个串的最长公共子串 || 或者n个串)的更多相关文章

  1. SPOJ 1811 Longest Common Substring 后缀自动机

    模板来源:http://www.neroysq.com/?p=76 思路:http://blog.sina.com.cn/s/blog_7812e98601012dfv.html 题意就是求两个字符串 ...

  2. 求两个字符串的最长公共子串——Java实现

    要求:求两个字符串的最长公共子串,如“abcdefg”和“adefgwgeweg”的最长公共子串为“defg”(子串必须是连续的) public class Main03{ // 求解两个字符号的最长 ...

  3. SPOJ 1811. Longest Common Substring (LCS,两个字符串的最长公共子串, 后缀自动机SAM)

    1811. Longest Common Substring Problem code: LCS A string is finite sequence of characters over a no ...

  4. ●SPOJ 1811 Longest Common Substring

    题链: http://poj.org/problem?id=2774 题解: 求两个字符串(S,T)的最长公共子串.对 S串建后缀自动机.接下来就用这个自动机去求出能和 S串匹配的 T的每一个前缀的最 ...

  5. SPOJ 1811 Longest Common Substring (后缀自动机第一题,求两个串的最长公共子串)

    题目大意: 给出两个长度小于等于25W的字符串,求它们的最长公共子串. 题目链接:http://www.spoj.com/problems/LCS/ 算法讨论: 二分+哈希, 后缀数组, 后缀自动机. ...

  6. SPOJ 1811 Longest Common Substring

    Description 给出两个字符串,求最长公共子串. Sol SAM. 这题随便做啊...后缀数组/Hash+二分都可以. SAM就是模板啊...直接在SAM上跑就行,没有了 \(go[w]\) ...

  7. [URAL-1517][求两个字符串的最长公共子串]

    Freedom of Choice URAL - 1517 Background Before Albanian people could bear with the freedom of speec ...

  8. 【java】求两个字符串的最长公共子串

    这个是华为OJ上的一道题目.首先,如果我们用java写代码,华为OJ有以下三条规则需遵守,否则编译无法通过或者用例无法通过,规则如下: (1)一定不可以有包名: (2)主类名只能为Main: (3)不 ...

  9. 求两个字符串的最长公共子串(LCS)

    http://tianyunpu2008.blog.163.com/blog/static/6559379920089162236915/

随机推荐

  1. 10个最新手机美食APP界面设计欣赏

    移动软件时代,简单下载美食app,动动手指,滑动几下手机屏幕,即可足不出户,搜索,预定和购买各路美食.然而,对于作为手机app UI 界面设计师的你来说,最大的问题并不在于如何使用这些美食软件来方便生 ...

  2. 设计模式(java)--Bridge模式之蜡笔与毛笔的故事

    转自:吕震宇 http://www.cnblogs.com/zhenyulu/articles/67016.html#!comments 我想大家小时候都有用蜡笔画画的经历吧.红红绿绿的蜡笔一大盒,根 ...

  3. Smarty的基本语法------变量调节器

    (1)首字母大写capitalize示例:{$articleTitle|capitalize}(2)字符串连接 cat示例:{$articleTitle|cat:" yesterday.&q ...

  4. shared_ptr / weak_ptr 代码片段

    参考<<Boost程序库完全开放指南>> shared_ptr  类摘要(只列出了常用的部分)和相关说明 template <class T> class shar ...

  5. CircRNA 环化RNA

    2016国自然新秀CircRNA的研究策略和分析  

  6. 从jvm运行机制来分析 String对象负值

    测试1: 代码 public class Test { public static void main(String[] args) { String s1="aaa"; f(s1 ...

  7. LinqPad介绍,下载,用法说明

    介绍一款用于Linq运算和测试的工具,LinqPad.我感觉这个工具非常优秀,不只是功能上优秀,在使用上也非常优秀,让我爱不释手. LinqPad官方地址:http://www.linqpad.net ...

  8. alpha七天冲刺计划

    alpha七天冲刺计划(更新ing) 第一天:https://www.cnblogs.com/renluqian/p/9895895.html 第二天: 第三天: 第四天: 第五天: 第六天: 第七天 ...

  9. [CentOS]使用yum命令报出Error: Cannot retrieve repository metadata (repomd.xml) for repository的解决方法

    在一次错误的repo文件rpm -i 之后,执行yum就开始报出 Error: Cannot retrieve repository metadata (repomd.xml) for reposit ...

  10. python之文件操作总结

    目录 文件:数据的抽象和集合 文件的打开关闭 文件内容的读取 文件的全文本操作 文件的逐行操作 数据文件的写入 使用json模块 文件:数据的抽象和集合 文件是存储在辅助存储器上的数据序列 文件是数据 ...