题意

一个长为 \(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. C#中用OLEDB操作EXCEL时,单元格内容长度超过255被截断

    C#中Microsoft.ACE.OLEDB.12.0 驱动读取excel,会读取前8行来判定每列的数据类型,假如没有超过255个字符,那么会被设置为nvarchar(255),从第9行开始,超过25 ...

  2. vue-router 注意事项

    1.vue-router 两种模式 (1)mode:hash,hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件.vue默认为hash模式 window.onh ...

  3. Linux mount 命令

    mount 命令用来挂载文件系统.其基本命令格式为:mount -t type [-o options] device dirdevice:指定要挂载的设备,比如磁盘.光驱等.dir:指定把文件系统挂 ...

  4. Centos7下部署两套python版本并存环境的操作记录

    需求说明:centos7.2系统的开发机器上已经自带了python2.7版本,但是开发的项目中用的是python3.5版本,为了保证Centos系统的正常运行,以及节省机器资源(不想因此再申请另外一台 ...

  5. Docker inspect - format格式化输出 - 运维笔记

    Docker --format 参数提供了基于 Go模板 的日志格式化输出辅助功能,并提供了一些内置的增强函数. 什么是模板?上图是大家熟悉的 MVC 框架(Model View Controller ...

  6. 分布式监控系统Zabbix-3.0.3-完整安装记录 - 添加shell脚本监控

    对公司的jira访问状态进行监控,当访问状态返回值是200的时候,脚本执行结果为1:其他访问状态返回值,脚本执行结果是0.然后将该脚本放在zabbix进行监控,当非200状态时发出报警.jira访问状 ...

  7. FormData

    1. 概述FormData类型其实是在XMLHttpRequest 2级定义的,它是为序列化表以及创建与表单格式相同的数据(当然是用于XHR传输)提供便利. 2. 构造函数创建一个formData对象 ...

  8. js实现随机的四则运算题目(2)-更新界面

    上次的代码提交完成后,有很多bug.比如函数会重复调用执行,每点击一次按钮都会在生成题目的下方直接生成新的题目,于是我在代码前面添加了如下的代码: function play_allE() { doc ...

  9. combox的基本应用

    easyui-combox:控件的初始化: 可以在其中进行文字的筛选功能(过滤), 动态加载数据的方法. <!DOCTYPE html><html lang="en&quo ...

  10. vs感受,由于我的电脑装了俩年了!我直接写感受吧

    个人感受:最初的感觉,最开始装vs是因为我的电脑8.1不兼容vc6.0(一个挺坑的编程软件),最开始用vs的时候我还是一个小白什么都不懂,vs创建项目实在是太复杂,不看教程根本看不懂,也许是它能包容的 ...