题意

一个长为 \(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

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

\[\displaystyle \mathrm{ans}=\min(d-c+1,\max_{i=a}^{b} \{\min(\mathrm{LCP}(i, c),b-i+1)\})
\]

我们发现 直接求这个 \(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

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

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

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

\[\displaystyle \mathrm{ans}=\min(d-c+1,\max_{i=a}^{b} \{\min(\mathrm{LCP}(i, c),b-i+1)\})
\]

我们考虑向 \(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;
}

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

  1. BZOJ 4556 [Tjoi2016&Heoi2016]字符串 ——后缀数组 ST表 主席树 二分答案

    Solution 1: 后缀数组暴力大法好 #include <map> #include <cmath> #include <queue> #include &l ...

  2. Bzoj 4556: [Tjoi2016&Heoi2016]字符串

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 177  Solved: 92[Sub ...

  3. bzoj 4556 [Tjoi2016&Heoi2016]字符串——后缀数组+主席树

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4556 本来只要查 ht[ ] 数组上的前驱和后继就行,但有长度的限制.可以二分答案解决!然后 ...

  4. ●BZOJ 4556 [Tjoi2016&Heoi2016]字符串

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=4556 题解: 巨恶心...但是题很好呀,可以练习好几个比较麻烦的算法~ 1).预处理 首先用 ...

  5. BZOJ3277 串(后缀数组+二分答案+主席树)

    因为不会SAM,考虑SA.将所有串连起来并加分隔符,每次考虑计算以某个位置开始的子串有多少个合法. 对此首先二分答案,找到名次数组上的一个区间,那么只需要统计有多少个所给串在该区间内出现就可以了.这是 ...

  6. 4556: [Tjoi2016&Heoi2016]字符串

    4556: [Tjoi2016&Heoi2016]字符串 链接 分析: 首先可以二分这个长度.此时需要判断是否存在一个以b结尾的前缀,满足与[c,d]的lcp大于等于mid. 如果我们把串翻转 ...

  7. BZOJ5343: [Ctsc2018]混合果汁 二分答案+主席树

    分析: 整体二分或二分答案+主席树,反正没有要求强制在线,两个都可以做... 贪心还是比较显然的,那么就是找前K大的和...和CQOI的任务查询系统很像 附上代码: #include <cstd ...

  8. BZOJ_5343_[Ctsc2018]混合果汁_二分答案+主席树

    BZOJ_5343_[Ctsc2018]混合果汁_二分答案+主席树 题意:给出每个果汁的价格p,美味度d,最多能放的体积l.定义果汁混合后的美味度为果汁的美味度的最小值. m次询问,要求花费不大于g, ...

  9. 2019杭电多校第四场hdu6621 K-th Closest Distance(二分答案+主席树)

    K-th Closest Distance 题目传送门 解题思路 二分答案+主席树 先建主席树,然后二分答案mid,在l和r的区间内查询[p-mid, p+mid]的范围内的数的个数,如果大于k则说明 ...

随机推荐

  1. 前端知识点总结(html+css)部分

    HTML 1.一套规则,浏览器认识的规则. 2.开发者: 学习Html规则 开发后台程序: - 写Html文件(充当模板的作用) ****** - 数据库获取数据,然后替换到html文件的指定位置(W ...

  2. 把List<T>转换为DataTable

    下面这个学习,把List<T>转换为Datatable. 下面先创建一个对象T: class Ay { private int _ID; public int ID { get { ret ...

  3. 学习angularjs的内置API函数

    angularjs的内置API函数有很多,如isString()判断给定的对象是否为字符串,如果是返回 true,反之返回false:isNumber()判断给定的对象是否为数字,如果是返回 true ...

  4. linux配置iptables(3)

    简单通用 web 服务器iptables 配置 *filter :INPUT DROP [0:0]:FORWARD DROP [0:0]:OUTPUT ACCEPT [0:0] #超出 链规则 的数据 ...

  5. Luogu P4016 负载平衡问题

    传说中的网络流24题之一,我刷的第二题菜. 据说这种东西做完了就可以有质的飞越?不过看着这些Luogu评级就有点蒙蔽. 首先我们看一下题目发现这不是均分纸牌的加强板吗,但是那个环的操作极大地限制了我的 ...

  6. [Oracle]发生 ora-06502 RMAN 在对 catalog DB 同期时出错的调查方法

    Catalog DB resync error: 1, setting on the server that starts the RMAN client $ Export EVENT_10928 = ...

  7. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(六)一定要RESTful吗?

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 写在前面的话 这个问题看起来就显得有些萌,或者说类似的问题都有些不靠 ...

  8. 【nodejs】让nodejs像后端mvc框架(asp.net mvc )一样处理请求--路由限制及选择篇(2/8)【route】

    文章目录 前情概要 上文中的RouteHandler中有一个重要方法GetActionDescriptor没有贴代码和说,接下来我们就说一说这个方法. 使用controllerName.actionN ...

  9. ssh登陆服务器locale告警(-bash: warning: setlocale:)的处理方法

    使用ssh远程登陆 IDC机房服务器,发现老是出现如下告警信息: -bash: warning: setlocale: LC_CTYPE: cannot change locale (en_US.UT ...

  10. tomcat内存溢出问题记录

    问题说明:公司内网环境中部署的jenkins代码发版平台突然不能访问了,查看tomcat的catalina.out日志发现报错如下: [root@redmine logs]# tail -f /srv ...