【字符串】【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. gcc/g++ -O 优化选项说明

    查查gcc手册就知道了,每个编译选项都控制着不同的优化选项 下面从网络上copy过来的,真要用到这些还是推荐查阅手册 -O设置一共有五种:-O0.-O1.-O2.-O3和-Os. 除了-O0以外,每一 ...

  2. 彩虹表(rainbow table)

    前记 MD5的全称是Message-Digest Algorithm 5(信息-摘要算法): 特点是不可逆的,一般解密不了:那有没有想过,为什么各种工具网站都可以进行MD5解密呢?https://ww ...

  3. 【1】hexo+github搭建个人博客的过程记录

    前提: 1.新建一个github仓库 2.安装配置Node.js 3.安装配置Git 前提 步骤1.新建一个github仓库 打开github网站,(注册)登录账号,新建一个仓库; 注:仓库名称要求, ...

  4. spring-session(一)揭秘续篇

    上一篇文章中介绍了Spring-Session的核心原理,Filter,Session,Repository等等,传送门:spring-session(一)揭秘. 这篇继上一篇的原理逐渐深入Sprin ...

  5. 手写LRU实现

    完整基于 Java 的代码参考如下 class DLinkedNode { String key; int value; DLinkedNode pre; DLinkedNode post; } LR ...

  6. 「福利」Java Swing 编写的可视化算法工程,包含树、图和排序

    之前在整理<学习排序算法,结合这个方法太容易理解了>这篇文章时,发现了一个用 Java Swing 编写的可视化算法工程,真心不错!包含了常用数据结构和算法的动态演示,先来张图感受下: 可 ...

  7. Bootstrap导航栏示例

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. javascript获取url中的参数值

    javascript的实现代码如下: function QueryString(fieldName) { var urlString = document.location.search; if(ur ...

  9. 【转载】C#的ArrayList使用Contains方法判断是否包含某个元素

    在C#的编程开发中,ArrayList集合是一个常用的非泛型类集合,在ArrayList集合中可以使用Contains方法判断是否包含某个元素数据,如果包含则返回true,否则返回false,Cont ...

  10. TP5.1 调用common里面自定义的常量

    公共文件:\application\common.php define('cms_password', cms); 控制器引用: 调用: $aa = cms_password; dump(cms_pa ...