2021-2022 ACM-ICPC Brazil Subregional Programming Contest B Beautiful Words

题目描述

https://codeforces.com/gym/103388/problem/B

简要题意:给定一个长度为 $n$ 的模板串 $S$ 和 $m$ 个查询串 $T_i$,现在对 $S$ 做 $m$ 次循环左移,令 $Q_i$ 表示第 $i$ 次循环左移的结果,$Q_i=S_iS_{i+1}\cdots S_nS_1S_2\cdots S_{i-2}S_{i-1}$,同时令 $Q_i$ 的贡献为 $Q_i$ 的长度最长的一个子串的长度,满足这个子串也是某个 $T_i$ 的子串,现在要求所有 $Q_i$ 的贡献的最小值

$n,m,|S|,\sum |T_i|\le 10^5$

Solution

我们考虑首先将 $S$ 复制一遍,这样以 $S_i$ 开始的长度为 $n$ 的子串就是 $Q_i$,然后我们对所有 $T$ 建广义后缀自动机,这样我们可以在枚举 $S$ 前缀的同时得到当前前缀的最长后缀,满足这个后缀是某个 $T$ 的子串

我们考虑二分答案,那么对于每个枚举出的前缀的后缀,我们发现满足答案的 $Q_i$ 的 $i$ 是一个区间,那么我们直接做一个区间覆盖即可判断,这里的所有区间覆盖可以直接通过差分来做

时间复杂度 $O(n\log n)$

```cpp

include

include

include

include

include

include

include

define maxn 100010

define Maxn 200010

define lowbit(i) ((i) & (-i))

define ll long long

using namespace std;

int n, m, a[maxn 2];
char s[maxn], t[maxn
2];

struct SAM {
int cnt, L, l, nxt[26];
} T[Maxn]; int f[Maxn], top, last, rt;
void init_SAM() {
for (int i = 1; i <= top; ++i) {
T[i].cnt = T[i].L = T[i].l = f[i] = 0;
fill(T[i].nxt, T[i].nxt + 26, 0);
}
rt = last = top = 1;
T[rt].L = T[rt].l = f[rt] = 0;
}

int extend(int p, int ch) {
if (T[p].nxt[ch]) {
int q = T[p].nxt[ch], nq;
if (T[q].L - 1 == T[p].L) return q;
nq = ++top; T[nq].L = T[p].L + 1; f[nq] = f[q];
memcpy(T[nq].nxt, T[q].nxt, sizeof T[q].nxt);
while (p && T[p].nxt[ch] == q) T[p].nxt[ch] = nq, p = f[p];
f[q] = nq; return nq;
}
int np = ++top; T[np].L = T[p].L + 1;
while (p && !T[p].nxt[ch]) T[p].nxt[ch] = np, p = f[p];
if (!p) return (f[np] = rt, np);
int q = T[p].nxt[ch];
if (T[q].L - 1 == T[p].L) f[np] = q;
else {
int nq = ++top; T[nq].L = T[p].L + 1;
f[nq] = f[q]; memcpy(T[nq].nxt, T[q].nxt, sizeof T[q].nxt);
while (p && T[p].nxt[ch] == q) T[p].nxt[ch] = nq, p = f[p];
f[np] = f[q] = nq;
}
return np;
}

void insert(char *s) {
int l = strlen(s), p = rt;
for (int i = 0; i < l; ++i) p = extend(p, s[i] - ‘a’);
}

int d[maxn];
bool check(int x) {
for (int i = 1; i <= n; ++i) d[i] = 0;
for (int i = 1; i < 2 * n; ++i) {
if (a[i] < x) continue; int l = i - a[i] + 1, r = i;
int L = max(1, l + x - 1 - n + 1), R = min(n, r - x + 1);
++d[L]; —d[R + 1];
} int s = 0;
for (int i = 1; i <= n; ++i) d[i] += d[i - 1], s += d[i] > 0;
return s == n;
}

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);

cin >> n >> m >> t + 1; init_SAM();
for (int i = 1; i <= m; ++i) cin >> s, insert(s); 
for (int i = n + 1; i < 2 * n; ++i) t[i] = t[i - n];
int p = rt, len = 0; 
for (int i = 1; i < 2 * n; ++i) {
    int ch = t[i] - 'a';
    if (T[p].nxt[ch]) p = T[p].nxt[ch], ++len;
    else {
        while (p && !T[p].nxt[ch]) p = f[p];
        if (!p) p = rt, len = 0;
        else len = T[p].L + 1, p = T[p].nxt[ch];
    } a[i] = min(len, n);
}
int l = 0, r = n, mid, ans;
while (l <= r) {
    mid = l + r >> 1;
    if (check(mid)) ans = mid, l = mid + 1;
    else r = mid - 1; 
} cout << ans << "\n";
return 0;

}