前言
完成如下概念的认识:自动机,状态,转移函数。这些都是相关的理论,可以参看黑书或者算法导论等。
分析
- Reg(s) 表示s这个状态能够接受的字符串的集合。
- Right(s) 如果a在S[l, r)出现,则它能够识别从r开始的后缀。
Right(s) 是一个集合,表示所有S[l, r)中的r。
有了Right(s),只要给出字符串长度len,就能确定所有串。
len一定是一个区间,我们称之为[Min(s), Max(s)]。 - 可以证明 Right(s) 只有独立和包含关系(这就证明了总复杂度是O(n))的。
- Right就构成了一棵树,令Parent(s)表示其祖先,称这棵树为Parent树,Parent(s)表示包含s状态的Right集合且最小的状态。
- 为什么Min(s) – 1不行? 因为出现位置超过了Right(s)了。那么Max(Parent(s)) = Min(s) – 1。
- 考虑转移f(s, c) = t,那么由于多了一个字符c,s的Right集合中,只有S[ri] = c才能符合条件。
所以t的Right集合是 {r_i+1 | s[r_i] == c}。
而且有一个显然的推论:Max(t) > Max(s)。 - 令fa = Parent(s),若s有出发标号为x的边,那么fa也有,且Right(f(s, c)) 包含于 Right(f(fa, c))。
构造
- 这是一个在线构造法。
- 设源串是T,新字符是x,则新串是Tx。
- 考虑Right集合中包含Len(T)的集合,v1, v2, …, vk
必然存在一个Right(p) = {L} 的节点p(f(T)),那么v1, v2, …, vk都可以用Parent函数找到。 - 构造新状态Right(np) = {L+1}
- 不妨我们假设v1 = p, v2 = Parent(v1), …, vk = root。
考虑一个v的集合Right = {r1, r2, …, rn = L},
如果在它后面加字符x的话,新状态nv只有s[ri] = x才满足。
我们知道,如果vi没有出发指向x的边,那么就没有这样的ri。
而我们又知道,如果vi有x的边,那么所有vi+1也有。 - 对于没有到x的边的,连一条到np的边。
- 令vp是第一个有x边的状态。考虑其集合Right(vp) = {r1, r2, r3, …, rn}。
令q = f(vp, x),
如果Max(q) 恰好等于 Max(vp) + 1,那么直接把Parent(np)设为q;
否则,就说明会新建点,我们新建点nq,Right(nq) = Right(q) ∪ {L+1},
则,Parent(q) = nq(q是nq的真子集),
并且Parent(np) = nq,
由于区间包含等等等关系可以证明Parent(nq) = Parent(q)。 - 由于转移的时候结束位置是不起作用的,所以f(nq, *) = f(q, *)。
最后,我们需要把所有f(vi, x) = q的点全部指向nq。
代码
const int MaxNode = 10000; const int MaxChar = 30; int go[MaxNode][MaxChar]; int val[MaxNode], pnt[MaxNode]; int chr[MaxNode]; int init, last, tot; inline void init_automachine(void) { last = init = tot = 1; } void extend_automachine(int c) { int p = last, np = ++tot; val[np] = val[p] + 1; chr[np] = c; while (p && go[p][c] == 0) go[p][c] = np, p = pnt[p]; if (p == 0) pnt[np] = init; else { int q = go[p][c]; if (val[p] + 1 == val[q]) pnt[np] = q; else { int nq = ++tot; val[nq] = val[p] + 1; chr[nq] = c; for (int j = 0; j < MaxChar; ++j) go[nq][j] = go[q][j]; pnt[nq] = pnt[q], pnt[q] = nq, pnt[np] = nq; while (p && go[p][c] == q) go[p][c] = nq, p = pnt[p]; } } last = np; } int main(void) { freopen("suffix_automachine.in", "r", stdin); freopen("output.txt", "w", stdout); int T; scanf("%dn", &T); char s[10000], t[10000]; for (int i = 0; i < T; ++i) { gets(s), gets(t); int n = strlen(s), m = strlen(t); init_automachine(); for (int i = 0; i < n; ++i) extend_automachine(s[i] - 'a'); int ans = m; for (int i = 0, cur = init; i < m; ++i) if (go[cur][t[i] - 'a'] != 0) cur = go[cur][t[i] - 'a']; else { ans = i; break; } cout << ans << endl; } return 0; }