bzoj4698
后缀自动机
辣鸡四平OJ
就是多串LCS,有点像AC自动机。先对一个串建立自动机,然后让其他串在上面跑。我们从根节点开始走,每次看儿子是否有这种字符,有的话直接向下走,步数+1,否则沿着par走,直到有为止。par的过程其实是不断缩短当前串来继续匹配。最后按parent树从叶子向上进行dp,每个点保存一个所有串匹配到这里的最小值,如果一个位置被访问过,那么他的父结点的dp值肯定就是val了,因为这个点都能走到,那么父结点肯定也能满足。
把拓扑序和字符串数组搞混了。。。
#include<bits/stdc++.h> using namespace std; const int N = 4005; int n, m, answer; int a[N], b[N], c[N], f[N], ans[N], tmp[N]; namespace SAM { int sz, last, root; struct node { int val, par; map<int, int> ch; } t[N]; int nw(int x) { t[++sz].val = x; return sz; } void iniSAM() { sz = 0; root = last = nw(0); } void RadixSort() { memset(c, 0, sizeof(c)); for(int i = 1; i <= sz; ++i) ++c[t[i].val]; for(int i = 1; i <= sz; ++i) c[i] += c[i - 1]; for(int i = sz; i; --i) tmp[c[t[i].val]--] = i; for(int i = 1; i <= sz; ++i) ans[i] = t[i].val; } void extend(int c) { int p = last, np = nw(t[p].val + 1); while(p && !t[p].ch[c]) t[p].ch[c] = np, p = t[p].par; if(!p) t[np].par = root; else { int q = t[p].ch[c]; if(t[q].val == t[p].val + 1) t[np].par = q; else { int nq = nw(t[p].val + 1); t[nq].ch = t[q].ch; t[nq].par = t[q].par; t[q].par = t[np].par = nq; while(p && t[p].ch[c] == q) t[p].ch[c] = nq, p = t[p].par; } } last = np; } } using namespace SAM; int main() { scanf("%d%d", &n, &m); iniSAM(); for(int i = 1; i <= m; ++i) scanf("%d", &b[i]); for(int i = 1; i < m; ++i) a[i] = b[i + 1] - b[i], extend(a[i]); RadixSort(); for(int i = 1; i < n; ++i) { scanf("%d", &m); for(int j = 1; j <= m; ++j) scanf("%d", &b[j]); for(int j = 1; j < m; ++j) a[j] = b[j + 1] - b[j]; int u = root, sum = 0; for(int j = 1; j < m; ++j) { int c = a[j]; if(t[u].ch.count(c)) u = t[u].ch[c], f[u] = max(f[u], ++sum); else { while(u && !t[u].ch.count(c)) u = t[u].par; if(!u) u = root, sum = 0; else { sum = t[u].val; u = t[u].ch[c]; f[u] = max(f[u], ++sum); } } } for(int j = sz; j; --j) { int u = tmp[j]; ans[u] = min(ans[u], f[u]); if(f[u] && t[u].par) f[t[u].par] = t[t[u].par].val; f[u] = 0; } } for(int i = 2; i <= sz; ++i) answer = max(answer, ans[i]); printf("%d\n", answer + 1); return 0; }