【字符串】【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. 31,Leetcode下一个排列 - C++ 原地算法

    题目描述 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列. 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列). 必须原地修改,只允许使用额外常 ...

  2. Prometheus Alertmanager 介绍详解

    Prometheus 之 Alertmanager 介绍详解 告警无疑是监控中非常重要的环节,虽然监控数据可视化了,也非常容易观察到运行状态.但我们很难做到时刻盯着监控,所以程序来帮巡检并自动告警,这 ...

  3. 深入浅出 REST(转)

    文章讲的不错,更具体一些,对实践的指导意义更强 原文:https://www.infoq.cn/article/rest-introduction/ 不知你是否意识到,围绕着什么才是实现异构的应用到应 ...

  4. WPF 精修篇 动态资源

    原文:WPF 精修篇 动态资源 动态资源 使用 DynamicResource 关键字 静态 就是 StaticResource 原则上是 能用静态就用静态 动态会让前台界面压力很大~ 动态资源引用 ...

  5. DateTime的ToString方法格式

    新建一个.NET Core控制台项目,敲入如下代码: using System; namespace NetCoreDatetime { class Program { static void Mai ...

  6. WPF WebBrowser抑制Suppress 弹出 脚本错误 对话框 但是样式改变 需要继续改善

    1.添加引用 using System.Reflection;using System.Windows.Controls; 2.静态类扩展方法(this) public static class We ...

  7. 性能监控工具的配置及使用 - Spotlight On Oracle(oracle)

    一.    Spotlight On Oracle(oracle)1.1.   工具简介Spotlight是一个强有力的Oracle数据库实时性能诊断工具,提供了一个直观的.可视化的数据库活动展现.S ...

  8. Java面试必问通信框架NIO,原理详解

    NIO 流与块 通道与缓冲区 缓冲区状态变量 文件 NIO 实例 选择器 套接字 NIO 实例 内存映射文件 NIO与IO对比 Path Files NIO 新的输入/输出 (NIO) 库是在 JDK ...

  9. 强化Linux 服务器的7个步骤

    这篇入门文章将向你介绍基本的 Linux 服务器安全知识.虽然主要针对 Debian/Ubuntu,但是你可以将此处介绍的所有内容应用于其他 Linux 发行版.我也鼓励你研究这份材料,并在适用的情况 ...

  10. React路由安装使用和多种方式传参

    安装路由 npm i react-router-dom -S 引入路由 import { BowserRouter as Router, Route, Switch, ... } from " ...