题意
n个字符串的最长公共子串。( n ≤ 10 , l e n ( s i ) ≤ 1 0 5 n \le 10,len(s_i) \le 10^5 n≤10,len(si)≤105)
思路
对第一个字符串建SAM,后面每读入一个串就把这个串在SAM上面跑,跑不动了就沿着 p a r e n t _ t r e e parent \_ tree parent_tree往上跳。每一个状态上取min,答案是所有状态取max。
注意
虽然是模板题但是还是WA了一下午。
1
让一个字符串在SAM上跑的代码如下:
void solve(char *s, int sz)
{
int u = 1, l = 0;
for (int i = 0; i < sz; ++ i){
int x = s[i]-'a';
if (ch[u][x]) ++ l, u = ch[u][x];
else{
while (u && !ch[u][x]) u = fa[u];
if (!u) u = 1, l = 0;
else l = len[u]+1, u = ch[u][x];
}
now[u] = max(now[u], l);
}
}
然后这是错误写法:
void solve(char *s, int sz)
{
int u = 1, v;
for (int i = 0; i < sz; ++ i){
int x = s[i]-'a';
while (fa[u] && !ch[u][x]) u = fa[u], now[u] = len[u];
v = ch[u][x];
if (v) now[v] = max(now[v], now[u]+1), u = v;
else u = 1;
}
}
区别在哪里呢?
可以看出第二种写法并没有用到变量 l l l,而是每次用 n o w [ u ] now[u] now[u]去更新 n o w [ v ] now[v] now[v]。然而很显然, n o w [ u ] now[u] now[u]指的是以当前节点结尾的最长子串, l l l指的是当前已经匹配的子串长度,在几次失配之后, n o w [ u ] now[u] now[u]和 l l l可能不相等。
比如这个样例:
SAM里面是aabc
待匹配的字符串是aababc
那么跑到第二个b
的时候
l
=
2
,
n
o
w
[
u
]
=
3
l=2,now[u]=3
l=2,now[u]=3,出锅。
2
沿着 p a r e n t _ t r e e parent\_tree parent_tree向上跳的时候,爬到的节点 l = l e n [ u ] l=len[u] l=len[u]。因为在 p a r e n t _ t r e e parent\_tree parent_tree上,父亲节点包含的任意子串均是儿子任意子串的后缀,所以假如能够跑到儿子,那么他父亲所包含的所有子串均是当前已匹配子串的后缀,所以能够匹配。
那么在什么情况下会出现 n o w [ f a [ u ] ] < l e n [ u ] now[fa[u]] < len[u] now[fa[u]]<len[u]呢?
看一组样例:
SAM里的字符串:abcbcc
需要识别的字符串:abcc
跑完前3个字符时,处在
r
i
g
h
t
(
u
)
=
{
3
}
right(u)=\{3\}
right(u)={3}的节点
u
u
u,此时下一个字符c
失配了,跳到
r
i
g
h
t
(
v
)
=
{
3
,
5
}
right(v)=\{3,5\}
right(v)={3,5},但是此时
v
v
v这个节点还从来没有被访问过,
n
o
w
(
v
)
=
0
now(v)=0
now(v)=0,出锅。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10, PN = N<<1, S = 26;
namespace SAM
{
int fa[PN], ch[PN][S], len[PN], cnt, last;
inline void reset(){
last = cnt = 1;
}
inline void copy(int x, int y){
for (int i = 0; i < S; ++ i)
ch[y][i] = ch[x][i];
fa[y] = fa[x];
}
inline void extend(int x){
x -= 'a';
int p = last, np = ++cnt;
last = np;
len[np] = len[p]+1;
while (p && !ch[p][x]) ch[p][x] = np, p = fa[p];
if (!p) fa[np] = 1;
else{
int q = ch[p][x];
if (len[q] == len[p]+1) fa[np] = q;
else{
int nq = ++cnt;
copy(q, nq);
len[nq] = len[p]+1;
fa[q] = fa[np] = nq;
while (p && ch[p][x] == q) ch[p][x] = nq, p = fa[p];
}
}
}
}
using namespace SAM;
int k, ans[PN], now[PN], id[PN], anss;
char s[N];
bool cmp(int x, int y){
return len[x] < len[y];
}
void solve(char *s, int sz)
{
int u = 1, l = 0;
for (int i = 0; i < sz; ++ i){
int x = s[i]-'a';
if (ch[u][x]) ++ l, u = ch[u][x];
else{
while (u && !ch[u][x]) u = fa[u];
if (!u) u = 1, l = 0;
else l = len[u]+1, u = ch[u][x];
}
now[u] = max(now[u], l);
}
}
int main()
{
scanf("%s", s);
reset();
for (int i = 0, sz = strlen(s); i < sz; ++ i)
extend(s[i]);
memset(ans, 0x7f, sizeof(ans));
for (int i = 1; i <= cnt; ++ i) id[i] = i;
sort(id+1, id+cnt+1, cmp);
while (~scanf("%s", s)){
if (s[0] == '0') break;
memset(now, 0, sizeof(now));
solve(s, strlen(s));
for (int j = cnt; j >= 1; -- j){
int x = id[j];
ans[x] = min(ans[x], now[x]);
if (fa[x] && now[x]) now[fa[x]] = len[fa[x]];
}
}
anss = 0;
for (int i = 1; i <= cnt; ++ i)
anss = max(anss, ans[i]);
printf("%d\n", anss);
return 0;
}