BZOJ2946 [Poi2000]公共串 【后缀自动机】
题目
给出几个由小写字母构成的单词,求它们最长的公共子串的长度。
任务:
l 读入单词
l 计算最长公共子串的长度
l 输出结果
输入格式
文件的第一行是整数 n,1<=n<=5,表示单词的数量。接下来n行每行一个单词,只由小写字母组成,单词的长度至少为1,最大为2000。
输出格式
仅一行,一个整数,最长公共子串的长度。
输入样例
3
abcb
bca
acbc
输出样例
2
题解
经典的SAM求多串LCP
首先对第一串建后缀自动机,然后用剩余的每一个串都在后缀自动机上跑一遍,分别得到后缀自动机上每个点的最大匹配长度
最后每个点取每个串匹配长度的最小值,最大的那个点就是答案
但要注意的是在parent树中,儿子的最大匹配值要向父亲传递,因为儿子所代表的位置集合是其父亲的真子集,父亲当然要包括儿子
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
using namespace std;
const int maxn = 4005,maxm = 100005,INF = 1000000000;
int pre[maxn],ch[maxn][26],step[maxn],cnt,last,n;
int g[maxn],f[maxn],a[maxn],b[maxn];
char s[maxn];
void ins(int x){
int p = last,np = ++cnt;
last = np; step[np] = step[p] + 1;
while (p && !ch[p][x]) ch[p][x] = np,p = pre[p];
if (!p) pre[np] = 1;
else {
int q = ch[p][x];
if (step[q] == step[p] + 1) pre[np] = q;
else {
int nq = ++cnt; step[nq] = step[p] + 1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
pre[nq] = pre[q]; pre[q] = pre[np] = nq;
while (ch[p][x] == q) ch[p][x] = nq,p = pre[p];
}
}
}
void topu(){
REP(i,cnt) b[step[i]]++;
REP(i,cnt) b[i] += b[i - 1];
REP(i,cnt) a[b[step[i]]--] = i;
REP(i,cnt) f[i] = step[i];
}
void walk(){
scanf("%s",s + 1); n = strlen(s + 1);
int u = 1,ans = 0,id;
REP(i,cnt) g[i] = 0;
for (int i = 1; i <= n; i++){
id = s[i] - 'a';
if (ch[u][id]) u = ch[u][id],g[u] = max(g[u],++ans);
else {
while (u != 1 && !ch[u][id]) u = pre[u];
if (u == 1) ans = 0;
else ans = step[u] + 1,u = ch[u][id],g[u] = max(g[u],ans);
}
}
for (int i = cnt; i; i--){
u = a[i];
if (pre[u]) g[pre[u]] = max(g[pre[u]],g[u]);
if (f[u] > g[u]) f[u] = g[u];
}
}
int main(){
int N; scanf("%d%s",&N,s + 1); N--;
cnt = last = 1; n = strlen(s + 1);
REP(i,n) ins(s[i] - 'a');
topu();
while (N--) walk();
int ans = 0;
REP(i,cnt) ans = max(ans,f[i]);
printf("%d\n",ans);
return 0;
}