【字符串】【P5830】 【模板】失配树

Description

给定一个长度为 \(n\) 的字符串 \(S\),有 \(m\) 次询问,每次询问给定 \(S\) 的两个前缀,求它们的最长公共 border 的长度。

最长公共 border 的含义为,对于一个字符串 \(T\),设其 Border 集合为所有既是 \(S\) 的前缀子串又是 \(S\) 的后缀子串的集合,两个字符串的最长公共 border 为两个字符串的 Border 集合的交集中长度最长的字符串。

Limitations

\(1 \leq n \leq 10^6\)

\(1 \leq m \leq 10^5\)

Solution

注意,这篇题解不是这个模板的标准做法,也不是最简单的做法。

两个前缀的最长公共 border 即为他们在 border 树上的 LCA

因为刚起床就被 fa姐姐 拉来验题,脑袋昏昏忘记了这个结论,只能再口胡一个铁憨憨做法。

注意到所求的 border 一定既是第一个字符串的后缀,又是第二个字符串的后缀,因此一定是两个字符串的公共后缀 ,同时注意到由于这两个字符串的前缀是相同的,所以如果一个字符串 \(T\) 既是其中任意一个串的 border,又是两个串的公共后缀,那么它一定是两个串的公共 border。并且这个条件显然也是必要条件,因此我们在求出两串的 lcp 以后只需要在其中任意一个串上找到其最长的长度不超过 lcp 长度的 border,那么该串即为两串的最长公共 border

假设我们已经求出了两串的 lcp 长度,那么问题就只剩下对一个字符串求其最长的长度不超过某数的 border

我们考虑对每个前缀,将它向它的最长 border 连一条边,那么显然这个图有 \((n + 1)\) 个节点, \(n\) 条边,又因为这个图是联通的,根据树的判定定理,这个图是一棵树,若规定 \(0\) 是这棵树的根,数学归纳可得每个节点的父节点为该节点所代表的前缀的最长 border。因为一个节点的 border 显然比该节点的长度小,所以任何一个节点到根所在的链上,若将节点按深度从小到大排列,则其所代表的前缀长度一定是单调递增的。因此我们只需要对整棵树进行 dfs,同时用一个栈维护当前节点到根的链,然后在栈里二分即可找到所求的串。

border 的方法见 【P3375】KMP字符串匹配

而求两个前缀的 lcp,可以对原串建立一个 SAM,两个前缀在 parent 树上所对应节点的 LCA 即为他们的 lcp。也可以将原串反过来,转化为求两个后缀的最长公共前缀,求出 SA 后用 height 数组解决。

但是扶苏既不愿意将原串反过来求 SA 在写个 ST,也担心毒瘤出题人卡了空间以后 SAM 建出来会爆空间,因此扶苏选择了 二分+hash 求出其 lcp

显然公共后缀的长度满足二分性,因此只要选择一个满足前缀可减性的 hash 函数就可以 \(O(1)\) check 了。

考虑时间复杂度:二分求 lcp 的复杂度是 \(O(m \log n)\),在 border 树上二分的复杂度是 \(O(m \log n)\),因此总时间复杂度 \(O(n + m \log n)\)。

Code

本来扶苏写了个四模数 hash,然后被卡常了就尝试减少模数个数,最后发现单模数就可以了(雾

#include <cstdio>
#include <vector>
#include <algorithm> const int maxh = 4;
const int maxm = 100005;
const int maxn = 1000005; const int MOD[] = {998244353, 1000000007, 1000000009, 1145141}; int n, m, top = -1;
char S[maxn];
int border[maxn], ans[maxm], stk[maxn];
std::vector<int> son[maxn], query[maxn]; struct HASH {
int md;
ll hash[maxn], inv[maxn]; ll mpow(const int a, int d, const int p) {
ll ret = 1, tmp = a;
while (d) {
if (d & 1) {
(ret *= tmp) %= p;
}
(tmp *= tmp) %= p;
d >>= 1;
}
return ret;
} void build(const int x) {
md = x;
ll tmp = 1, iv = mpow(100, x - 2, x);
inv[0] = 1;
for (int i = 1; i <= n; ++i) {
hash[i] = (hash[i - 1] + (S[i] - 'a') * tmp) % md;
inv[i] = inv[i - 1] * iv % md;
(tmp *= 100) %= md;
}
} bool check(const int x, const int y, const int len) {
ll h1 = (hash[x] - hash[x - len]) * inv[x - len] % md, h2 = (hash[y] - hash[y - len]) * inv[y - len] % md;
if (h1 < 0) h1 += md;
if (h2 < 0) h2 += md;
if (h1 != h2) {
return false;
} else {
return true;
}
}
};
HASH h[maxh]; int ReadStr(char *p);
void dfs(const int u); int main() {
freopen("1.in", "r", stdin);
n = ReadStr(S);
for (int i = 0; i < maxh; ++i) {
h[i].build(MOD[i]);
}
for (int i = 2, j = 0; i <= n; ++i) {
while (j && (S[j + 1] != S[i])) {
j = border[j];
}
if (S[j + 1] == S[i]) {
++j;
}
son[border[i] = j].push_back(i);
}
son[0].push_back(1);
qr(m);
for (int p, q, Ans, i = 1; i <= m; ++i) {
p = q = Ans = 0; qr(p); qr(q);
for (int l = 1, r = std::min(p, q) - 1, mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) {
bool flag = true;
for (int i = 0; i < maxh; ++i) if ((flag = h[i].check(p, q, mid)) == false) {
break;
}
if (flag) {
l = (Ans = mid) + 1;
} else {
r = mid - 1;
}
}
ans[i] = Ans;
query[std::min(p, q)].push_back(i);
}
dfs(0);
for (int i = 1; i <= m; ++i) {
qw(ans[i], '\n', true);
}
return 0;
} int ReadStr(char *p) {
auto beg = p;
do *(++p) = IPT::GetChar(); while ((*p >= 'a') && (*p <= 'z'));
*p = 0;
return p - beg - 1;
} void dfs(const int u) {
stk[++top] = u;
for (auto v : query[u]) {
int w = ans[v]; ans[v] = 0;
for (int l = 1, r = top, mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) if (stk[mid] <= w) {
ans[v] = stk[mid];
l = mid + 1;
} else {
r = mid - 1;
}
}
for (auto v : son[u]) {
dfs(v);
}
--top;
}

【字符串】【P5830】 【模板】失配树的更多相关文章

  1. 字符串hash与字典树

    title: 字符串hash与字典树 date: 2018-08-01 22:05:29 tags: acm 算法 字符串 概述 这篇主要是关于字符串里的 字符串hash 和 字符串字典树,,两个都是 ...

  2. 【AC自动机】【字符串】【字典树】AC自动机 学习笔记

    blog:www.wjyyy.top     AC自动机是一种毒瘤的方便的多模式串匹配算法.基于字典树,用到了类似KMP的思维.     AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串, ...

  3. 一类巧妙利用利用失配树的序列DP

    I.导入 求长度为\(\text{len}\)的包含给定连续子串\(\text{T}\)的 0/1 串的个数.(\(|T|<=15\)) 通常来说这种题目应该立刻联想到状压 DP 与取反集--这 ...

  4. P3384 【模板】树链剖分

    P3384 [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节 ...

  5. 洛谷P3368 【模板】树状数组 2

    P3368 [模板]树状数组 2 102通过 206提交 题目提供者HansBug 标签 难度普及/提高- 提交  讨论  题解 最新讨论 暂时没有讨论 题目描述 如题,已知一个数列,你需要进行下面两 ...

  6. 洛谷P3374 【模板】树状数组 1

    P3374 [模板]树状数组 1 140通过 232提交 题目提供者HansBug 标签 难度普及/提高- 提交  讨论  题解 最新讨论 题目描述有误 题目描述 如题,已知一个数列,你需要进行下面两 ...

  7. hdu 1754 I Hate It (模板线段树)

    http://acm.hdu.edu.cn/showproblem.php?pid=1754 I Hate It Time Limit: 9000/3000 MS (Java/Others)    M ...

  8. luogu3384 【模板】树链剖分

    P3384 [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节 ...

  9. 【BZOJ5496】[十二省联考2019]字符串问题(后缀树)

    [BZOJ5496][十二省联考2019]字符串问题(后缀树) 题面 BZOJ 洛谷 题解 首先显然可以把具有支配关系的串从\(A\)到\(B\)连一条有向边,如果\(B_i\)是\(A_j\)的前缀 ...

随机推荐

  1. nth-of-type(n)和nth-child(n)的区别

    nth-of-type 与nth-child都属于css选择器,都是在同级别节点中找到第n个元素,前者是先确定元素类型,再计算n的位置:后者是先确定n的位置,再确定元素类型 例: <div cl ...

  2. SQL -------- TOP 查询前几行

    SELECT TOP 子句用于指定要返回的记录数量.并不是所有的数据库系统都支持SELECT TOP子句.MySQL支持LIMIT子句来选择有限数量的记录,而Oracle使用ROWNUM. top 后 ...

  3. 【C++】STL各容器的实现,时间复杂度,适用情况分析

    一.vector 1.概述 动态数组,在内存中具有连续的储存空间,在堆上分配内存,支持快速随机访问,在中间插入和删除慢,但在末尾插入和删除快 2.特点 1)拥有一段连续的内存空间,并且起始地址不变,因 ...

  4. bean的shutdown

    使用@Bean注解,在不配置destroyMethod时,其默认值为: String destroyMethod() default AbstractBeanDefinition.INFER_METH ...

  5. 汇编语言01 - 打印 "Hello World!"

    Hello World! 源代码 data segment msg db "Hello World!$";定义名称为msg的字符串,最后加上$,表示字符串结束 data ends ...

  6. Ubuntu下SVN客户端RapidSVN

    Window下我们使用TortoiseSVN,可以很方便地进行查看.比较.更新.提交.回滚等SVN版本控制操作.在Linux下,我们可以使用rapidsvn. RapidSVN是一款不错的SVN客户端 ...

  7. K8s 学习者绝对不能错过的最全知识图谱(内含 58个知识点链接)

    作者 | 平名 阿里服务端开发技术专家 导读:Kubernetes 作为云原生时代的“操作系统”,熟悉和使用它是每名用户的必备技能.本篇文章概述了容器服务 Kubernetes 的知识图谱,部分内容参 ...

  8. 开发技术--Numpy模块

    开发|Numpy模块 Numpy模块是数据分析基础包,所以还是很重要的,耐心去体会Numpy这个工具可以做什么,我将从源码与 地产呢个实现方式说起,祝大家阅读愉快! Numpy模块提供了两个重要对象: ...

  9. ES6的常见语法!!

    let : 声明变量 不存在变量提前 拥有局部作用域 (只要有{}出现 则只在该{}范围内生效) (而var只在函数内会产生作用域范围) 不能重复声明 const : 声明常量(常量名从规范上来将 最 ...

  10. HTML Web Workers

    Web worker 是运行在后台的 JavaScript,不会影响页面的性能. 什么是 Web Worker? 当在 HTML 页面中执行脚本时,页面是不可响应的,直到脚本已完成. Web work ...