P4022 题解

· · 题解

前言

虽然是黑题,但是难度并不高。我是不是对黑题难度有什么误解

思路

f[i] 表示当前作文匹配完前 i 个字符的时候有多少字符在标准库出现过。

直接二分答案,然后 check 函数返回值即 f[len] * 10 >= len * 9。(要超过 90\% 嘛)

对于每篇作文,设 L[i] 表示最大的长度 j,满足 s[i-j,i] 在标准库里出现过。(代码中的 mxlen 数组)

$f[i]=\max(f[i-1],\displaystyle\max_{j=i-x}^{i-Len[i]}(f[j]-j+i))

x 表示当前二分的答案)

后面接的这段字符串长度显然不能超过最大匹配 $L[i]$,所以上限是 $i-L[i]$。 然后就是 $L[]$ 怎么求? 对所有子串建广义 SAM,然后跳 parent 树就行。 $i-L[i]$ 明显单调递增(因为前一个串包含后一个串),然后 dp 式中,$f[j]-j$ 和 $i$ 没关系,剩余部分 $i$ 也是单调递增。 所以整个式子满足决策单调性,可以单调队列搞。 ## 代码 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2.2e6 + 5; int n, m; struct SAM { int tot, last, fa[N], len[N], t[N][2]; int q[N], f[N], mxlen[N]; inline SAM() { tot = last = 0; fa[0] = -1; } inline void insert(char c) { c ^= 48; int cur = ++tot; len[cur] = len[last] + 1; int p = last; while (~p && !t[p][c]) t[p][c] = cur, p = fa[p]; last = cur; if (!~p) { fa[cur] = 0; return; } int x = t[p][c]; if (len[p] + 1 == len[x]) fa[cur] = x; else { len[++tot] = len[p] + 1; fa[tot] = fa[x]; t[tot][0] = t[x][0], t[tot][1] = t[x][1]; while (~p && t[p][c] == x) t[p][c] = tot, p = fa[p]; fa[x] = fa[cur] = tot; } } inline void match(string s) { int cur = 0, res = 0; for (int i = 0; i < s.size(); i++) { int c = s[i] ^ 48; if (t[cur][c]) res++, cur = t[cur][c]; else { while (~cur && !t[cur][c]) cur = fa[cur]; if (!~cur) res = cur = 0; else res = len[cur] + 1, cur = t[cur][c]; } mxlen[i + 1] = res; } } inline bool check(int x, int len) { for (int i = 0; i < x; i++) f[i] = 0; for (int i = x, h = 1, t = 0; i <= len; i++) { while (h <= t && f[i - x] - (i - x) > f[q[t]] - q[t]) t--; q[++t] = i - x; while (h <= t && q[h] < i - mxlen[i]) h++; if (h <= t) f[i] = max(f[i - 1], f[q[h]] + i - q[h]); } return f[len] * 10 >= len * 9; } } sam; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m; string s; for (int i = 1; i <= m; i++) { cin >> s; sam.last = 0; for (char c : s) sam.insert(c); } for (int i = 1; i <= n; i++) { cin >> s; sam.match(s); int l = 1, r = s.size(), len = s.size(); while (l <= r) { int mid = l + r >> 1; if (sam.check(mid, len)) l = mid + 1; else r = mid - 1; } cout << l - 1 << '\n'; } return 0; } ```