Address
https://www.lydsy.com/JudgeOnline/problem.php?id=2806
Solution
Q:如何判断一个串是否是标准串之一的子串?
A:将标准串建成一棵 Trie ,然后在 Trie 上建立后缀自动机,即广义后缀自动机。从初态开始, for 输入串的每一个字符,每次都向对应的字符边转移,如果能转移到则输入串是标准串之一的子串。
回到原问题,先把标准串建成广义后缀自动机。
然后对于每个询问,二分答案进行 DP :
设当前判定的答案为
mid
m
i
d
。
f[i]
f
[
i
]
表示到串的长度为
i
i
的前缀的所有划分方法中,熟悉的子串的长度总和的最大值。
可以用后缀自动机计算出以 为结尾,在标准串中作为子串出现过的最长子串长度
len[i]
l
e
n
[
i
]
。
计算方法:从
len[i−1]
l
e
n
[
i
−
1
]
转移。
len[0]=0
l
e
n
[
0
]
=
0
。在计算 len 的过程中,维护子串 [i-len+1,i] 在 SAM 中代表的状态,从
len[i−1]
l
e
n
[
i
−
1
]
转移到
len[i]
l
e
n
[
i
]
时,不断地跳 Parent ,直到当前状态能通过文章串第
i
i
个字符转移为止,这时候通过文章串第 个字符转移,得到
len[i]
l
e
n
[
i
]
。
所以得出朴素的转移:
从这个过程容易发现: i−len[i] i − l e n [ i ] 随 i i 单调不降。
所以转化一下:
用单调队列维护 f[j]−j f [ j ] − j 的最大值,就可以 O(1) O ( 1 ) 转移。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Sam(i, orz) for (; i && orz; i = SAM[i].fa)
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 11e5 + 5, M = N << 1;
int n, m, QAQ, QWQ, len, f[N], Q[N], H, T;
char s[N];
struct nodeTrie {
int go[2];
void init() {
memset(go, 0, sizeof(go));
}
} Trie[N];
struct nodeSAM {
int fa, maxl, go[2];
void init() {
memset(go, 0, sizeof(go));
}
} SAM[M];
void insTrie(int n, char *s) {
int i, u = 1;
For (i, 1, n) {
int c = s[i] - '0';
if (!Trie[u].go[c]) Trie[Trie[u].go[c] = ++QAQ].init();
u = Trie[u].go[c];
}
}
int insSAM(int c, int lst) {
int x = lst; SAM[lst = ++QWQ].init();
SAM[lst].maxl = SAM[x].maxl + 1;
Sam(x, !SAM[x].go[c]) SAM[x].go[c] = lst;
if (!x) return SAM[lst].fa = 1, lst;
int y = SAM[x].go[c];
if (SAM[x].maxl + 1 == SAM[y].maxl) return SAM[lst].fa = y, lst;
int p = ++QWQ; SAM[p] = SAM[y];
SAM[y].fa = SAM[lst].fa = p; SAM[p].maxl = SAM[x].maxl + 1;
Sam(x, SAM[x].go[c] == y) SAM[x].go[c] = p;
return lst;
}
void dfs(int u, int lst) {
int c; For (c, 0, 1) if (Trie[u].go[c])
dfs(Trie[u].go[c], insSAM(c, lst));
}
bool check(int mid) {
int i, u = 1, l = 0; H = T = 0;
For (i, 1, len) {
int c = s[i] - '0'; f[i] = f[i - 1];
while (u > 1 && !SAM[u].go[c])
u = SAM[u].fa, l = SAM[u].maxl;
if (SAM[u].go[c]) u = SAM[u].go[c], l++;
int le = i - l, ri = i - mid;
if (ri < 0) continue;
while (H < T && f[Q[T]] - Q[T] < f[ri] - ri) T--;
Q[++T] = ri;
while (H < T && Q[H + 1] < le) H++;
if (H < T) f[i] = max(f[i], f[Q[H + 1]] - Q[H + 1] + i);
}
return 10 * f[len] >= 9 * len;
}
int main() {
Trie[QAQ = 1].init(); SAM[QWQ = 1].init();
cin >> n >> m;
while (m--) scanf("%s", s + 1), insTrie(strlen(s + 1), s);
int i; dfs(1, 1);
while (n--) {
scanf("%s", s + 1); len = strlen(s + 1);
int l = 1, r = len;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) l = mid + 1;
else r = mid - 1;
}
printf("%d\n", r);
}
return 0;
}