SPOJ 1811 Longest Common Substring(求两个串的最长公共子串 || 或者n个串)

时间:2023-03-09 08:26:31
SPOJ 1811 Longest Common Substring(求两个串的最长公共子串 || 或者n个串)

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();
}