题意:给n个单词,m个串,问每个串满足条件的最长前缀,条件:该前缀S能被拆成几个连续的单词(S中的每个字符出现并且唯一出现在某一个单词中)
题解:有dp的思想,假设当前字符的位置为 j,如果当前字符能被理解,则存在 i ∈ (0, j),Si~Sj 是一个单词。因为单词的长度只有20可以暴力向前找。考虑AC自动机,一个节点的fail指向的是以当前串的最长后缀,所以沿着每个节点的fail指针不断向上跳,判断是否存在一个j就行了(单词长度较短,至多跳20次)。
struct node { int lengh, fail, Count, vis[26]; node() { mem(vis, 0); fail = Count = lengh = 0; } }; struct Acmation { int tot, use[2000010]; node a[300]; void Inite() { tot = 0; } void Insert(char *s) { int n =strlen(s); int now = 0; rep(i, 0, n) { int id = s[i] - 'a'; if (!a[now].vis[id]) a[now].vis[id] = ++tot; now = a[now].vis[id]; } a[now].Count++; a[now].lengh = n; } void getFail() { queue<int> q; rep(i, 0, 26) if (a[0].vis[i]) { a[a[0].vis[i]].fail = 0; q.push(a[0].vis[i]); } while(!q.empty()) { int now = q.front(); q.pop(); rep(i, 0, 26) { if (a[now].vis[i]) { a[a[now].vis[i]].fail = a[a[now].fail].vis[i]; q.push(a[now].vis[i]); } else { a[now].vis[i] = a[a[now].fail].vis[i]; } } } } void Query(char *s) { mem(use, 0); use[0] = 1; int n = strlen(s); int now = 0; rep(i, 0, n) { int id = s[i] - 'a'; now = a[now].vis[id]; for (int j = now; j; j = a[j].fail) if (a[j].Count) use[i] |= use[i - a[j].lengh]; } int ans = 0; rep(i, 0, n) if (use[i]) ans = max(ans, i + 1); pr(ans); } }; int n, m; Acmation ac; int main() { ac.Inite(); sc(n), sc(m); rep(i, 0, n) { char s[20]; scanf("%s", s); ac.Insert(s); } ac.getFail(); char s[2000010]; rep(i, 0, m) { scanf("%s", s); ac.Query(s); } return 0; }