BZOJ 4556: [Tjoi2016&Heoi2016]字符串(后缀数组 + 二分答案 + 主席树 + ST表 or 后缀数组 + 暴力)

题意

一个长为 n 的字符串 s,和 m 个询问。每次询问有 4 个参数分别为 a,b,c,d

要你告诉它 s[a...b] 中的所有子串 和 s[c...d] 的 最长公共前缀 (LCP) 的最大值。

(1n,m105,ab,cd,1a,b,c,dn)

题解

一开始看错了题 以为是 [a,b] 中所有子串 和 [c,d] 中所有子串的 LCP 这怎么能做啊!!!

仔细观察了一下 发现是 [a,b] 的所有子串 和 [c,d] 。。。。

那么题目就变 简单 了一点。。。

首先我们考虑与 [c,d] 有最长 LCP 的在哪里

不难发现 就是后缀排序后 rk[i]rk[c] 最靠近的 i

那么我们可以转化求 [a,b] 中的这个 i 就行了qwq

答案表示出来大概是这样子的。

ans=min(dc+1,maxi=ab{min(LCP(i,c),bi+1)})

我们发现 直接求这个 i 会被后面的 bi+1 限制掉 所以不能直接这样求

但我们可以考虑转化一下 我们考虑 二分答案 如果判断一个答案是否存在就容易一些了

我们考虑二分这个长度 假设是 len 那么前面的 i 就只能存在于 [a,blen+1] 这个区间内

然后看 rk[c] 周围连续的 height[q]len 可以延伸到哪个范围 这个东西 直接用 ST 可以实现

怎么实现呢 类似于倍增的思想 就是把那段距离看成一串二进制 然后从高到低去消掉一个个数就行了

得到这个区间 [sl,sr] 后 我们就需要查找里面是否存在 [a,blen+1] 的元素 这个东西就直接上 主席树 就行了

最后时间复杂度就是 O(nlog2n) 咯(令 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; }

彩蛋

细心的读者肯定发现了 这个代码的 resultTLE 2333

为什么呢 本人常数巨大啊!!!

但这份代码交到 luogu 上不开 O2 是 8000ms 开了是 4000ms

我突然想看看别人怎么写的 然后查找一波最优解 诶 有个叫 yyb_test 的神犇 只要 400ms

我仔细翻阅了一波大佬的代码 惊了 这不是暴力吗!!!

这才有了我现在的标题 (后缀数组 + 暴力)

我们继续考虑之前答案的那个式子

ans=min(dc+1,maxi=ab{min(LCP(i,c),bi+1)})

我们考虑向 rk[c] 前后去扫一下得到答案

其中如果此处 sa[i][a,b] 之中的话我们就计入答案就行了。

然后就有一个史诗级优化 就是当前扫的 heightmin 值 如果不优于当前的 ans

我们就可以轻易退出循环啦 这由于数据较为随机 所以 height 就比较降的比较快 所以就比较快了qwq

理论 时间复杂度 O(nq) 实际(随机数据) 时间复杂度 O(std10) 23333

放个对比图2333

pic

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; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/8727385.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(376)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示