[kuangbin]后缀自动机题解
学了后缀自动机,推荐几个比较好的学习资料:
- clj老师的NOI现场讲稿PPT,很详细但是不是很好懂
- Menci大佬的博客,讲解上更数学(这是好事.jpg),而且还有很好的配图方便理解
- 经典俄文教程的翻译,讲解很好懂,不过读起来就有点像外国教材那种废话很多的感觉……
SPOJ - LCS
题意
最长公共子串,字符串长度$ \le 250000$,字符集为小写字母,时限294 ms
解题思路
用后缀数组的话,可以用非法字符连接AB后建出SA,求出LCP最大值即可,复杂度\(O(N)\) (DC3)
不确定能不能过,因为DC3不太好写就不试了,反正是为了练后缀自动机
用SAM的话,可以对A串建出SAM,然后让B串在SAM上运行,如果某次转移\(c\)失配,那么就沿着后缀连接转移,直到当前节点可以匹配\(c\)。在这个过程中维护匹配的子串长度,如果成功转移就\(+1\),沿着后缀连接移动就变成当前节点的\(max\)值(节点表示的字符串的最大长度)
但是这个时限实在是太紧了,而且SPOJ很慢……,需要很优秀的SAM实现才能过这题,我使用Menci的用动态申请点实现SAM的板子怎么都过不了,看来必须静态写法才能过这题,最后在博客上当了一份比较好的SAM实现:
#include <cstdio>
#include <cstring>
#include <algorithm>
using std::max;
using std::min;
using std::copy;
using std::fill;
const int MAX_N = 250009;
struct Node {
static Node buf[], *bufp;
void *operator new(size_t) {return bufp++;}
int lim;
Node *ch[26], *f;
Node() = default;
Node(int lim_): lim(lim_) {
fill(ch, ch+26, nullptr);
}
} Node::buf[MAX_N*4], *Node::bufp=buf, *root, *last;
void append(char c) {
int x = c-'a';
Node *now=new Node(last->lim+1), *p;
for (p=last; p && !p->ch[x]; p=p->f)
p->ch[x] = now;
if (!p) {
now->f = root;
} else {
Node *q = p->ch[x];
if (q->lim == p->lim+1) {
now->f = q;
} else {
Node *r = new Node(p->lim+1);
copy(q->ch, q->ch+26, r->ch);
r->f = q->f;
q->f = now->f = r;
for (; p && p->ch[x]==q; p=p->f)
p->ch[x] = r;
}
}
last = now;
}
char a[MAX_N], b[MAX_N];
int main() {
scanf("%s%s", &a, &b);
last = root = new Node(0);
for (char *p=a; *p; ++p)
append(*p);
Node *now = root;
int ans = 0, pref = 0;
for (char *p=b; *p; ++p) {
int x = *p-'a';
for (; now && !now->ch[x]; now=now->f)
;
if (!now) {
now = root;
pref = 0;
continue;
}
pref = min(pref, now->lim)+1;
ans = max(ans, pref);
now = now->ch[x];
}
printf("%d\n", ans);
}
用了不少优秀的技巧,比如重载new
运算符实现内存池,用指针遍历字符串来少求一次strlen
等
学习一个.jpg