【字符串】【P5830】 【模板】失配树
【字符串】【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】 【模板】失配树的更多相关文章
- 字符串hash与字典树
title: 字符串hash与字典树 date: 2018-08-01 22:05:29 tags: acm 算法 字符串 概述 这篇主要是关于字符串里的 字符串hash 和 字符串字典树,,两个都是 ...
- 【AC自动机】【字符串】【字典树】AC自动机 学习笔记
blog:www.wjyyy.top AC自动机是一种毒瘤的方便的多模式串匹配算法.基于字典树,用到了类似KMP的思维. AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串, ...
- 一类巧妙利用利用失配树的序列DP
I.导入 求长度为\(\text{len}\)的包含给定连续子串\(\text{T}\)的 0/1 串的个数.(\(|T|<=15\)) 通常来说这种题目应该立刻联想到状压 DP 与取反集--这 ...
- P3384 【模板】树链剖分
P3384 [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节 ...
- 洛谷P3368 【模板】树状数组 2
P3368 [模板]树状数组 2 102通过 206提交 题目提供者HansBug 标签 难度普及/提高- 提交 讨论 题解 最新讨论 暂时没有讨论 题目描述 如题,已知一个数列,你需要进行下面两 ...
- 洛谷P3374 【模板】树状数组 1
P3374 [模板]树状数组 1 140通过 232提交 题目提供者HansBug 标签 难度普及/提高- 提交 讨论 题解 最新讨论 题目描述有误 题目描述 如题,已知一个数列,你需要进行下面两 ...
- 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 ...
- luogu3384 【模板】树链剖分
P3384 [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节 ...
- 【BZOJ5496】[十二省联考2019]字符串问题(后缀树)
[BZOJ5496][十二省联考2019]字符串问题(后缀树) 题面 BZOJ 洛谷 题解 首先显然可以把具有支配关系的串从\(A\)到\(B\)连一条有向边,如果\(B_i\)是\(A_j\)的前缀 ...
随机推荐
- spring boot中的日志入门
日志通常不会在需求阶段作为一个功能单独提出来,也不会在产品方案中看到它的细节.但是,这丝毫不影响它在任何一个系统中的重要地位. 报警系统与日志系统的关系 为了保证服务的高可用,发现问题一定要及时,定位 ...
- python]用eval强制将字符串转换为字典变量时候出错:NameError: name 'null' is not defined[python]用eval函数 字符串转dict
本博客已搬家至个人网站 在路上 - On the way 下面的 技术 分类. 你可以通过点击 更新帖子 [已解决]Python中,用eval强制将字符串转换为字典变量时候出错:NameError: ...
- 五、Hexo静态博客背景及界面显示优化配置
示例预览:我的主页 背景图片添加 自动切换背景 静态本地背景 首先将已选定的背景图片放到博客根目录下的\source\images下 示例:D:\Blog\source\images\backgr ...
- JS操作摄像头
<script src="javascript/jquery-1.9.1.min.js"></script> <fieldset> <le ...
- C#操作XML文档
Note: '=> ' 表示返回值 参考资料:请点击这里! 1:创建Xml文档 2:写Xml文档(必须保证有根元素) XmlDocument Xd (实例化一个对象) CreateXmlDecl ...
- JS删除指定下标的元素
在开发过程中,有时我们需要删除数组中某一下标的元素.JAVA中ArrayList有remove函数.但是在JavaScript中没有直接的删除方法.我们可以利用splice来实现.Array.spli ...
- open_basedir restriction in effect
vim /usr/local/nginx/conf/fastcgi.conf fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_ro ...
- INS-35178错误,AMM及ASMM区别
遇到这个报错,就使用asmm 一般先装库,再opatch到最新补丁,最后dbca建库,物理内存大于4G不能用AMM只能用ASMM 内存越大,全自动管理就越费劲,出错概率就越高,内存抖动 oracle的 ...
- SQL 分组后只获取每组的一条数据
/****** Object: Table [dbo].[TEMP] Script Date: 2018-8-22 星期三 23:33:09 ******/ SET ANSI_NULLS ON GO ...
- Elasticsearch高版本安装head插件
安装Elasticsearch 1.安装Elasticsearch-6.5.4.tar.gz [merce@info5 ~]$ cd /appmerce/zrapp/ [merce@info5 zra ...