最长公共子串 题解
Statement
Q7.1.2.4,时限 4s
给一个串,定义 \(\mathrm{LCS}\) 为最长公共子串长度,\(q\) 次询问,每次给出 \(l,r\),求
\(n\le 10^5,q\le 30\)
Solution
tag:SA,线段树维护分治结构,orz hunction
题目就是让我们快速算 \(\text{LCS}(S[l,p],S[p,r]),p\in[l..r]\)
发现没法硬求,考虑到 \(p\) 每移一格 \(\text{LCS}\) 最多变化 \(1\)
假装已知 \(\text{LCS}\) 上界,列出一个 \(\text{LCS}\) 式子:\(\displaystyle\text{LCS}=\max_{i\in[L..p-\text{LCS}+1],j\in[p..R-\text{LCS}+1]}\{|\text{LCP}(i,j)|\}\)
令集合 \(\text{Left}=[L..p-\text{LCS}+1],\text{Right}=[p..R-\text{LCS}+1]\)
考虑:
- \(\text{rk}(i)\) 作为 \(\text{ht}\) 区间的左端点时,应选择 \(\text{rk}(j)>\text{rk}(i)\) 中最小的 \(\text{rk}(j)\)
- \(\text{rk}(j)\) 作为 \(\text{ht}\) 区间的左端点时,应选择 \(\text{rk}(i)>\text{rk}(j)\) 中最小的 \(\text{rk}(i)\)
考虑在 \(\text{rk}\) 值域上分治计算答案
设当前为 \([l,r]\),中点为 \(mid\),算跨过中点的贡献,那么设:
- \([l..mid]\) 中来自 \(\text{Left}\) 集合的 \(\text{rk}\) 的最大值为 \(LLmx\)
- \([l..mid]\) 中来自 \(\text{Right}\) 集合的 \(\text{rk}\) 的最大值为 \(LRmx\)
- \([mid+1..r]\) 中来自 \(\text{Right}\) 集合的 \(\text{rk}\) 的最小值为 \(RRmn\)
- \([mid+1..r]\) 中来自 \(\text{Left}\) 集合的 \(\text{rk}\) 的最小值为 \(RLmn\)
那么答案一定来自区间 \([LLmx+1..RRmn]\) 或 \([LRmx+1..RLmn]\)
因为还要求动态修改 \(\text{Left}\) 和 \(\text{Right}\),考虑线段树维护这个分治结构
那么删点、加点都可以轻松实现,就可以维护答案了
但是 \(\text{Left},\text{Right}\) 的定义是假装我们知道 \(\text{LCS}\) 的
\(\text{Left}=[L..p-\text{LCS}+1],\text{Right}=[p..R-\text{LCS}+1]\)
这时设上一个 \(\text{LCS}\) 为 \(\text{LCS}'\)
分别把 \(l=\text{LCS}'+1,\text{LCS}',\text{LCS}'-1\) 代入 \(\text{Left},\text{Right}\) 算一遍答案,记为 \(\text{Ans}\)
若 \(\text{Ans}\ge l\),那么 \(\text{LCS}=l\)
为什么是 \(\ge\) ?因为我拍了下,当 bababab
,\(p=4\) 时代入式子算出的是 \(4\) 而不是 \(3\)
证明可以分类讨论
做完了!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, j, k) for (int i = (j); i <= (k); ++i)
#define reo(i, j, k) for (int i = (j); i >= (k); --i)
typedef long long ll;
const int N = 4e5 + 10, LN = 20, INF = 2e9;
int n, q;
string s;
int m = 131, sa[N], rk[N], se[N], cnt[N], height[N];
void Rsort() {
rep(i, 1, m) cnt[i] = 0;
rep(i, 1, n) ++cnt[rk[i]];
rep(i, 1, m) cnt[i] += cnt[i - 1];
reo(i, n, 1) sa[cnt[rk[se[i]]]--] = se[i];
}
void SA() {
if (n == 1) return (void)(sa[1] = rk[1] = 1);
rep(i, 1, n) rk[i] = s[i - 1], se[i] = i;
Rsort();
for (int w = 1, p; w < n; w <<= 1, m = p) {
p = 0;
rep(i, n - w + 1, n) se[++p] = i;
rep(i, 1, n) if (sa[i] > w) se[++p] = sa[i] - w;
Rsort(), swap(rk, se), p = 0;
rep(i, 1, n)
if (se[sa[i]] == se[sa[i - 1]] && se[sa[i] + w] == se[sa[i - 1] + w]) rk[sa[i]] = p;
else rk[sa[i]] = ++p;
if (p == n) break;
}
}
int ln, Mn[N][LN];
void Getheight() {
int k = 0;
rep(i, 1, n) {
if (k) --k;
while (s[i + k - 1] == s[sa[rk[i] - 1] + k - 1]) ++k;
height[rk[i]] = k;
}
}
void initST() {
ln = __lg(n);
rep(i, 1, n) Mn[i][0] = height[i];
rep(j, 1, ln)
rep(i, 1, n - (1 << j) + 1)
Mn[i][j] = min(Mn[i][j - 1], Mn[i + (1 << (j - 1))][j - 1]);
}
int LimitL, LimitR;
int AskMn(int l, int r) {
if (!(2 <= l && l <= r && r <= n)) return 0;
int k = __lg(r - l + 1);
int ans = min(Mn[l][k], Mn[r - (1 << k) + 1][k]);
return ans;
}
void init() {
SA(), Getheight(), initST();
}
#define lc (u << 1)
#define rc ((u << 1) | 1)
#define mid ((l + r) >> 1)
struct Item {
int LeftMx, LeftMn, RightMx, RightMn, ans;
Item(int _LMx = -INF, int _LMn = INF, int _RMx = -INF, int _RMn = INF, int _ans = 0) {
LeftMx = _LMx, LeftMn = _LMn, RightMx = _RMx, RightMn = _RMn, ans = _ans;
}
Item operator+ (const Item& u) const {
int res = max({ans, u.ans, AskMn(LeftMx + 1, u.RightMn), AskMn(RightMx + 1, u.LeftMn)});
return Item(max(LeftMx, u.LeftMx), min(LeftMn, u.LeftMn), max(RightMx, u.RightMx), min(RightMn, u.RightMn), res);
}
} f[N << 2];
void up(int u) {
f[u] = f[lc] + f[rc];
}
void build(int u, int l, int r) {
f[u] = Item();
if (l == r) return;
build(lc, l, mid), build(rc, mid + 1, r);
}
void addLeft(int u, int l, int r, int x) {
if (x < l || r < x) return;
if (l == r) {
f[u].LeftMn = min(f[u].LeftMn, x);
f[u].LeftMx = max(f[u].LeftMx, x);
if (f[u].RightMn != -INF) f[u].ans = 1;
else f[u].ans = 0;
return;
}
addLeft(lc, l, mid, x), addLeft(rc, mid + 1, r, x), up(u);
}
void addRight(int u, int l, int r, int x) {
if (x < l || r < x) return;
if (l == r) {
f[u].RightMn = min(f[u].RightMn, x);
f[u].RightMx = max(f[u].RightMx, x);
if (f[u].LeftMn != -INF) f[u].ans = 1;
else f[u].ans = 0;
return;
}
addRight(lc, l, mid, x), addRight(rc, mid + 1, r, x), up(u);
}
void delRight(int u, int l, int r, int x) {
if (x < l || r < x) return;
if (l == r) return (void)(f[u].RightMn = INF, f[u].RightMx = -INF, f[u].ans = 0);
delRight(lc, l, mid, x), delRight(rc, mid + 1, r, x), up(u);
}
#undef lc
#undef rc
#undef mid
void solve() {
int L, R;
cin >> L >> R;
LimitR = R;
int LCS = 1, ans = 2;
build(1, 1, n);
int LeftR = L, RightR = R;
addLeft(1, 1, n, rk[L]);
rep(k, L, R) addRight(1, 1, n, rk[k]);
rep(p, L + 1, R) {
LimitL = p;
delRight(1, 1, n, rk[p - 1]);
auto check = [&](int lcs) {
int targetLeftR = p - lcs + 1, targetRightR = max(p, R - lcs + 1);
while (LeftR < targetLeftR) addLeft(1, 1, n, rk[++LeftR]);
while (RightR < targetRightR) addRight(1, 1, n, rk[++RightR]);
while (RightR > targetRightR) delRight(1, 1, n, rk[RightR--]);
return min({p - L + 1, R - p + 1, max(1, f[1].ans)}) >= lcs;
};
if (check(LCS + 1)) ++LCS;
else if (check(LCS)) ;
else if (check(LCS - 1)) --LCS;
ans ^= p - L + 1 + LCS;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cin >> n >> q >> s, init();
while (q--) solve();
return 0;
}