[SPOJ1812]Longest Common Substring II
[SPOJ1812]Longest Common Substring II
试题描述
A string is finite sequence of characters over a non-empty finite set Σ.
In this problem, Σ is the set of lowercase letters.
Substring, also called factor, is a consecutive sequence of characters occurrences at least once in a string.
Now your task is a bit harder, for some given strings, find the length of the longest common substring of them.
Here common substring means a substring of two or more strings.
输入
输出
The length of the longest common substring. If such string doesn't exist, print "0" instead.
输入示例
alsdfkjfjkdsal
fdjskalajfkdsla
aaaajfaaaa
输出示例
2
数据规模及约定
见“输入”
题解
考虑把所有串连接起来构建后缀自动机(之前一看到这种题果断想后缀数组),那么我们在 parent 树上处理出每个节点子树中包含的串的集合。那么最后扫一遍每个节点,对于所有满的集合用 Max[i] 更新答案就好了。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <algorithm> using namespace std; int read() { int x = 0, f = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); } return x * f; } #define maxn 2000110 #define maxa 40 int rt, ToT, last, ch[maxn][maxa], par[maxn], Max[maxn], id[maxn]; char Str[maxn]; int S[maxn], Id[maxn], len; void extend(int i) { int x = S[i], p = last, np = ++ToT; Max[np] = Max[p] + 1; id[np] = (Id[i] <= 10 ? (1 << Id[i]) : 0); last = np; while(p && !ch[p][x]) ch[p][x] = np, p = par[p]; if(!p){ par[np] = rt; return ; } int q = ch[p][x]; if(Max[q] == Max[p] + 1){ par[np] = q; return ; } int nq = ++ToT; Max[nq] = Max[p] + 1; memcpy(ch[nq], ch[q], sizeof(ch[q])); par[nq] = par[q]; par[np] = par[q] = nq; while(p && ch[p][x] == q) ch[p][x] = nq, p = par[p]; return ; } int sa[maxn], Ws[maxn]; void build() { for(int i = 1; i <= ToT; i++) Ws[len-Max[i]]++; for(int i = 1; i <= len; i++) Ws[i] += Ws[i-1]; for(int i = ToT; i; i--) sa[Ws[len-Max[i]]--] = i; for(int i = 1; i <= ToT; i++) id[par[sa[i]]] |= id[sa[i]]; return ; } int main() { rt = last = ToT = 1; int cnt = 0; while(~scanf("%s", Str)) { int n = strlen(Str); for(int i = 0; i < n; i++) S[++len] = Str[i] - 'a', Id[len] = cnt; S[++len] = cnt + 26; Id[len] = cnt + 11; cnt++; } for(int i = 1; i <= len; i++) extend(i); build(); int all = (1 << cnt) - 1, ans = 0; for(int i = 1; i <= ToT; i++) if(id[i] == all) ans = max(ans, Max[i]); printf("%d\n", ans); return 0; }
然而 SPOJ 卡常数 T 飞~~~
考虑优化,因为这个最长公共子串一定出现在某一个字符串中,所以我们只需要对第一个串构造 SAM,对于后面的串,我们分别扫一遍在 SAM 上转移,然后再每个节点上更新一下当前匹配的最长长度信息,然后取取 min max 什么的就好了。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <algorithm> using namespace std; int read() { int x = 0, f = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); } return x * f; } #define maxn 200010 #define maxa 26 int rt, ToT, last, ch[maxn][maxa], par[maxn], Max[maxn], Ans[maxn], mn[maxn]; char Str[maxn]; int len; void extend(int i) { int x = Str[i] - 'a', p = last, np = ++ToT; Max[np] = Max[p] + 1; last = np; while(p && !ch[p][x]) ch[p][x] = np, p = par[p]; if(!p){ par[np] = rt; return ; } int q = ch[p][x]; if(Max[q] == Max[p] + 1){ par[np] = q; return ; } int nq = ++ToT; Max[nq] = Max[p] + 1; memcpy(ch[nq], ch[q], sizeof(ch[q])); par[nq] = par[q]; par[np] = par[q] = nq; while(p && ch[p][x] == q) ch[p][x] = nq, p = par[p]; return ; } int sa[maxn], Ws[maxn]; void build() { for(int i = 1; i <= ToT; i++) Ws[len-Max[i]]++; for(int i = 1; i <= len; i++) Ws[i] += Ws[i-1]; for(int i = ToT; i; i--) sa[Ws[len-Max[i]]--] = i; return ; } void get(int n) { int p = rt, tmp = 0; memset(Ans, 0, sizeof(Ans)); for(int i = 0; i < n; i++) { int x = Str[i] - 'a'; if(ch[p][x]) p = ch[p][x], tmp++; else { while(p && !ch[p][x]) p = par[p]; if(!p) tmp = 0, p = 1; else tmp = Max[p] + 1, p = ch[p][x]; } Ans[p] = max(Ans[p], tmp); } for(int i = 1; i <= ToT; i++) { int u = sa[i]; mn[u] = min(mn[u], Ans[u]); if(par[u] && Ans[u]) Ans[par[u]] = Max[par[u]]; } return ; } int main() { rt = last = ToT = 1; scanf("%s", Str); len = strlen(Str); for(int i = 0; i < len; i++) extend(i); build(); for(int i = 1; i <= ToT; i++) mn[i] = Max[i]; while(~scanf("%s", Str)) get(strlen(Str)); int ans = 0; for(int i = 1; i <= ToT; i++) ans = max(ans, mn[i]); printf("%d\n", ans); return 0; }