题意
一个长为 \\(n\\) 的字符串 \\(s\\),和 \\(m\\) 个询问。每次询问有 \\(4\\) 个参数分别为 \\(a,b,c,d\\)。
要你告诉它 \\(s[a...b]\\) 中的所有子串 和 \\(s[c...d]\\) 的 最长公共前缀 \\((\\mathrm{LCP})\\) 的最大值。
\\((1\\le n,m\\le 10^5, a\\le b,c\\le d,1\\le a,b,c,d\\le n)\\)
题解
一开始看错了题 以为是 \\([a,b]\\) 中所有子串 和 \\([c,d]\\) 中所有子串的 \\(\\mathrm{LCP}\\) 这怎么能做啊!!!
仔细观察了一下 发现是 \\([a,b]\\) 的所有子串 和 \\([c,d]\\) 。。。。
那么题目就变 简单 了一点。。。
首先我们考虑与 \\([c,d]\\) 有最长 \\(\\mathrm{LCP}\\) 的在哪里
不难发现 就是后缀排序后 \\(rk[i]\\) 与 \\(rk[c]\\) 最靠近的 \\(i\\) 。
那么我们可以转化求 \\([a,b]\\) 中的这个 \\(i\\) 就行了qwq
答案表示出来大概是这样子的。
我们发现 直接求这个 \\(i\\) 会被后面的 \\(b-i+1\\) 限制掉 所以不能直接这样求
但我们可以考虑转化一下 我们考虑 二分答案 如果判断一个答案是否存在就容易一些了
我们考虑二分这个长度 假设是 \\(len\\) 那么前面的 \\(i\\) 就只能存在于 \\([a,b-len+1]\\) 这个区间内
然后看 \\(rk[c]\\) 周围连续的 \\(height[q]\\ge len\\) 可以延伸到哪个范围 这个东西 直接用 \\(\\mathrm{ST}\\) 表 可以实现
怎么实现呢 类似于倍增的思想 就是把那段距离看成一串二进制 然后从高到低去消掉一个个数就行了
得到这个区间 \\([sl,sr]\\) 后 我们就需要查找里面是否存在 \\([a,b-len+1]\\) 的元素 这个东西就直接上 主席树 就行了
最后时间复杂度就是 \\(O(n \\log^2 n)\\) 咯(令 \\(n,q\\) 同级)
思路就很清晰了 但是代码就一点也不好写。。。 先挂出来吧。。
代码
/**************************************************************
Problem: 4556
User: zjp_shadow
Language: C++
Result: Time_Limit_Exceed
****************************************************************/
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == \'-\') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * fh;
}
void File() {
#ifdef zjp_shadow
freopen ("4556.in", "r", stdin);
freopen ("4556.out", "w", stdout);
#endif
}
const int N = 2e6 + 1e3;
struct Suffix_Array {
int sa[N], tmp[N], rk[N], n, m, c[N];
char str[N];
void Init(int n, char bas[]) { this -> n = n; Cpy(str, bas); }
inline void Radix_Sort() {
For (i, 1, m) c[i] = 0;
For (i, 1, n) ++ c[rk[i]];
For (i, 1, m) c[i] += c[i - 1];
Fordown (i, n, 1) sa[c[rk[tmp[i]]] --] = tmp[i];
}
inline void Build_Sa() {
For (i, 1, n) rk[i] = str[i], tmp[i] = i;
m = 255; Radix_Sort();
for (register int k = 1, p; k <= n; k <<= 1) {
p = 0;
For (i, n - k + 1, n) tmp[++ p] = i;
For (i, 1, n) if (sa[i] > k) tmp[++ p] = sa[i] - k;
Radix_Sort(); swap(rk, tmp);
rk[sa[1]] = 1, m = 1;
For (i, 2, n) rk[sa[i]] = (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + k] == tmp[sa[i - 1] + k]) ? m : ++ m;
if (m >= n) return ;
}
}
int height[N];
inline void Get_Height() {
for (int i = 1, j, k = 0; i <= n; ++ i) {
if (k) -- k;
j = sa[rk[i] - 1];
while (str[i + k] == str[j + k]) ++ k;
height[rk[i]] = k;
}
}
} SA;
struct Chairman_Tree {
int ls[N], rs[N], tot[N], Size, rt[N];
void Insert(int &o, int pre, int l, int r, int up) {
o = ++ Size; ls[o] = ls[pre]; rs[o] = rs[pre];
tot[o] = tot[pre] + 1; if (l == r) return;
int mid = (l + r) >> 1;
if (up <= mid) Insert(ls[o], ls[pre], l, mid, up);
else Insert(rs[o], rs[pre], mid + 1, r, up);
}
bool Query(int s, int t, int l, int r, int ql, int qr) {
int now = tot[t] - tot[s];
if (!now) return false;
if (ql <= l && r <= qr) return true;
int mid = (l + r) >> 1;
if (ql <= mid && Query(ls[s], ls[t], l, mid, ql, qr)) return true;
if (qr > mid && Query(rs[s], rs[t], mid + 1, r, ql, qr)) return true;
return false;
}
} CT;
struct Sparse_Table {
int minv[N][20], Log[N];
void Build(int n, int a[]) {
For (i, 1, n)
minv[i][0] = a[i], Log[i] = Log[i >> 1] + 1;
For (j, 1, Log[n])
For (i, 1, n - (1 << j) + 1)
minv[i][j] = min(minv[i][j - 1], minv[i + (1 << (j - 1))][j - 1]);
}
} ST1, ST2;
int n, m;
inline bool Check(int len, int a, int b, int c, int d) {
int sl = SA.rk[c], sr = SA.rk[c];
Fordown (i, ST2.Log[sl], 0)
if (ST2.minv[n - sl + 1][i] >= len) sl -= (1 << i);
Fordown (i, ST1.Log[n - sr + 1], 0)
if (ST1.minv[sr + 1][i] >= len) sr += (1 << i);
int ql = a, qr = b - len + 1;
return CT.Query(CT.rt[ql - 1], CT.rt[qr], 1, n, sl, sr);
}
inline int Get_Ans(int a, int b, int c, int d) {
int l = 1, r = min(b - a + 1, d - c + 1), ans = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (Check(mid, a, b, c, d)) l = mid + 1, ans = mid;
else r = mid - 1;
}
return ans;
}
int val1[N], val2[N];
char str[N];
int main () {
File();
n = read(); m = read();
scanf ("%s", str + 1);
SA.Init(n, str);
SA.Build_Sa();
SA.Get_Height();
For (i, 1, n) {
CT.Insert(CT.rt[i], CT.rt[i - 1], 1, n, SA.rk[i]);
val1[i] = val2[n - i + 1] = SA.height[i];
}
ST1.Build(n, val1);
ST2.Build(n, val2);
For (i, 1, m) {
int a = read(), b = read(), c = read(), d = read();
printf ("%d\\n", Get_Ans(a, b, c, d));
}
//cerr << (double) clock() /CLOCKS_PER_SEC << endl;
return 0;
}
彩蛋
细心的读者肯定发现了 这个代码的 result 是 \\(\\mathrm{TLE}\\) 2333
为什么呢 本人常数巨大啊!!!
但这份代码交到 \\(luogu\\) 上不开 \\(O2\\) 是 8000ms 开了是 4000ms
我突然想看看别人怎么写的 然后查找一波最优解 诶 有个叫 yyb_test 的神犇 只要 400ms
我仔细翻阅了一波大佬的代码 惊了 这不是暴力吗!!!
这才有了我现在的标题 (后缀数组 + 暴力)
我们继续考虑之前答案的那个式子
我们考虑向 \\(rk[c]\\) 前后去扫一下得到答案
其中如果此处 \\(sa[i]\\) 在 \\([a,b]\\) 之中的话我们就计入答案就行了。
然后就有一个史诗级优化 就是当前扫的 \\(height\\) 的 \\(\\min\\) 值 如果不优于当前的 \\(\\mathrm{ans}\\)
我们就可以轻易退出循环啦 这由于数据较为随机 所以 \\(height\\) 就比较降的比较快 所以就比较快了qwq
理论 时间复杂度 \\(O(nq)\\) 实际(随机数据) 时间复杂度 \\(O(\\frac{\\mathrm{std}}{10})\\) 23333
放个对比图2333
BruteForce 代码
/**************************************************************
Problem: 4556
User: zjp_shadow
Language: C++
Result: Accepted
Time:1768 ms
Memory:59916 kb
****************************************************************/
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == \'-\') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * fh;
}
void File() {
#ifdef zjp_shadow
freopen ("4556.in", "r", stdin);
freopen ("4556.out", "w", stdout);
#endif
}
const int N = 2e6 + 1e3;
struct Suffix_Array {
int sa[N], tmp[N], rk[N], n, m, c[N];
char str[N];
void Init(int n, char bas[]) { this -> n = n; Cpy(str, bas); }
inline void Radix_Sort() {
For (i, 1, m) c[i] = 0;
For (i, 1, n) ++ c[rk[i]];
For (i, 1, m) c[i] += c[i - 1];
Fordown (i, n, 1) sa[c[rk[tmp[i]]] --] = tmp[i];
}
inline void Build_Sa() {
For (i, 1, n) rk[i] = str[i], tmp[i] = i;
m = 255; Radix_Sort();
for (register int k = 1, p; k <= n; k <<= 1) {
p = 0;
For (i, n - k + 1, n) tmp[++ p] = i;
For (i, 1, n) if (sa[i] > k) tmp[++ p] = sa[i] - k;
Radix_Sort(); swap(rk, tmp);
rk[sa[1]] = 1, m = 1;
For (i, 2, n) rk[sa[i]] = (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + k] == tmp[sa[i - 1] + k]) ? m : ++ m;
if (m >= n) return ;
}
}
int height[N];
inline void Get_Height() {
for (int i = 1, j, k = 0; i <= n; ++ i) {
if (k) -- k;
j = sa[rk[i] - 1];
while (str[i + k] == str[j + k]) ++ k;
height[rk[i]] = k;
}
}
} SA;
int n, m;
inline int Get_Ans(int a, int b, int c, int d) {
int ans = 0, len = min(b - a + 1, d - c + 1), tmp = len;
Fordown (i, SA.rk[c], 1) {
if (SA.sa[i] >= a && SA.sa[i] <= b)
chkmax(ans, min(tmp, b - SA.sa[i] + 1));
chkmin(tmp, SA.height[i]);
if (tmp <= ans) break;
}
tmp = len;
For (i, SA.rk[c] + 1, n) {
chkmin(tmp, SA.height[i]);
if (tmp <= ans) break;
if (SA.sa[i] >= a && SA.sa[i] <= b)
chkmax(ans, min(tmp, b - SA.sa[i] + 1));
}
return ans;
}
int val1[N], val2[N];
char str[N];
int main () {
File();
n = read(); m = read();
scanf ("%s", str + 1);
SA.Init(n, str);
SA.Build_Sa();
SA.Get_Height();
For (i, 1, m) {
int a = read(), b = read(), c = read(), d = read();
printf ("%d\\n", Get_Ans(a, b, c, d));
}
//cerr << (double) clock() /CLOCKS_PER_SEC << endl;
return 0;
}