bzoj5084 hashit 广义SAM+树链的并
题目传送门
https://lydsy.com/JudgeOnline/problem.php?id=5084
题解
考虑平常对于静态问题,我们应该如何用 SAM 求本质不同的子串个数。
对于一个常规的 SAM,这个东西应该是 \(\sum\limits_{i\in V} len_i - len_{fa_i}\)。
很容易发现,我们如果把这个字符串每一个时刻的前一个字符和后一个字符给连接起来,这是一个树的关系。
考虑对这个树建立一棵广义 SAM。
但是上面的结论在广义 SAM 中不适用。不适用的是 \(i\) 的条件。
如果固定了当前的串是树上的哪一条链,这里就不应该是 \(i \in V\) 了,而是 \(i\) 代表的子串(等价类)在这个串中出现过。
这个东西显然就是对于这个串,每一个前缀所在的 \(endpos\) 集合的等价类的点在 parent 树上的链的并的长度了。
因为每一次的添加或删除字符影响的只有一个点,所以维护 \(parent\) 树上的树链的并来实现。
时间复杂度 \(O(n\log n)\) 。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b , 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b , 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 2e5 + 7;
int n, Q, nod, dfc;
ll ans = 0;
char s[N], v[N];
int id[N], fa[N], ip[N], dis[N];
int f[N], dep[N], siz[N], son[N], dfn[N], pre[N], top[N];
struct Edge { int to, ne, w; } g[N]; int head[N], tot;
inline void addedge(int x, int y, int z) { g[++tot].to = y, g[tot].w = z, g[tot].ne = head[x], head[x] = tot; }
inline void adde(int x, int y, int z) { addedge(x, y, z), addedge(y, x, z); }
struct Node { int c[26], fa, len; } t[N];
inline int extend(int p, int x) {
if (t[p].c[x]) {
int q = t[p].c[x];
if (t[q].len == t[p].len + 1) return q;
int nq = ++nod;
t[nq] = t[q], t[nq].len = t[p].len + 1, t[q].fa = nq;
for (; t[p].c[x] == q; p = t[p].fa) t[p].c[x] = nq;
return nq;
}
int np = ++nod;
t[np].len = t[p].len + 1;
for (; p && !t[p].c[x]; p = t[p].fa) t[p].c[x] = np;
// dbg("p = %d, np = %d, nod = %d, x = %c\n", p, np, nod, x);
if (!p) t[np].fa = 1;
else {
int q = t[p].c[x];
if (t[q].len == t[p].len + 1) t[np].fa = q;
else {
int nq = ++nod;
t[nq] = t[q], t[nq].len = t[p].len + 1, t[q].fa = t[np].fa = nq;
for (; p && t[p].c[x] == q; p = t[p].fa) t[p].c[x] = nq;
}
}
// dbg("p = %d, np = %d, nod = %d, x = %c\n", p, np, nod, x);
assert(!t[1].fa);
return np;
}
inline void dfs1(int x, int fa = 0) {
f[x] = fa, dep[x] = dep[fa] + 1, siz[x] = 1;
for fec(i, x, y) if (y != fa) dfs1(y, x), siz[x] += siz[y], siz[y] > siz[son[x]] && (son[x] = y);
}
inline void dfs2(int x, int pa) {
top[x] = pa, dfn[x] = ++dfc, pre[dfc] = x;
if (!son[x]) return; dfs2(son[x], pa);
for fec(i, x, y) if (y != f[x] && y != son[x]) dfs2(y, y);
}
inline int lca(int x, int y) {
while (top[x] != top[y]) dep[top[x]] > dep[top[y]] ? x = f[top[x]] : y = f[top[y]];
return dep[x] < dep[y] ? x : y;
}
inline void build() {
ip[1] = nod = 1;
for (int i = 2; i <= n; ++i)
ip[i] = extend(ip[fa[i]], v[i] - 'a');
for (int i = 2; i <= nod; ++i) addedge(t[i].fa, i, t[i].len - t[t[i].fa].len), dis[i] = t[i].len;
// for (int i = 1; i <= nod; ++i) dbg("i = %d, t[i].fa = %d, t[i].len = %d\n", i, t[i].fa, t[i].len);
}
struct cmp {
inline bool operator () (const int &x, const int &y) { return dfn[x] < dfn[y]; }
};
std::set<int, cmp> st;
inline void ins(int x) {
std::set<int, cmp>::iterator p = st.lower_bound(x);
int y = 0, z = 0;
ans += dis[x];
if (p != st.end()) y = *p, ans -= dis[lca(x, y)];
if (p != st.begin()) z = *--p, ans -= dis[lca(x, z)];
if (y && z) ans += dis[lca(y, z)];
st.insert(x);
}
inline void del(int x) {
st.erase(x);
std::set<int, cmp>::iterator p = st.lower_bound(x);
int y = 0, z = 0;
ans -= dis[x];
if (p != st.end()) y = *p, ans += dis[lca(x, y)];
if (p != st.begin()) z = *--p, ans += dis[lca(x, z)];
if (y && z) ans -= dis[lca(y, z)];
}
inline void work() {
build();
dfs1(1), dfs2(1, 1);
int now = 1;
for (int i = 1; i <= Q; ++i) {
if (s[i] == '-') del(ip[now]), now = fa[now];
else now = id[i], ins(ip[now]);
// dbg("now = %d, ip = %d\n", now, ip[now]);
printf("%lld\n", ans);
}
}
inline void init() {
scanf("%s", s + 1);
Q = strlen(s + 1);
int now = n = 1;
for (int i = 1; i <= Q; ++i) {
if (s[i] == '-') now = fa[now];
else fa[++n] = now, v[n] = s[i], now = n;
id[i] = now;
}
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
init();
work();
fclose(stdin), fclose(stdout);
return 0;
}
bzoj5084 hashit 广义SAM+树链的并的更多相关文章
- 51nod1600-Simple KMP【SAM,树链剖分】
正题 题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1600 题目大意 给出一个字符串\(s\),每次在最后插入一个字符后求它的 ...
- 【bzoj5084】hashit 广义后缀自动机+树链的并+STL-set
题目描述 你有一个字符串S,一开始为空串,要求支持两种操作 在S后面加入字母C 删除S最后一个字母 问每次操作后S有多少个两两不同的连续子串 输入 一行一个字符串Q,表示对S的操作 如果第i个字母是小 ...
- 【bzoj5084】 hashit(广义SAM+set)
题面 传送门 题解 后缀平衡树是个啥啊我不会啊-- 那么我们来考虑一下\(SAM\)的做法好了 不难发现它的本义是要我们维护一棵\(trie\)树,并求出\(trie\)树上每一个节点到根的这段串的不 ...
- CF G. Indie Album 广义后缀自动机+树链剖分+线段树合并
这里给出一个后缀自动机的做法. 假设每次询问 $t$ 在所有 $s$ 中的出现次数,那么这是非常简单的: 直接对 $s$ 构建后缀自动机,随便维护一下 $endpos$ 大小就可以. 然而,想求 $t ...
- 关于广义后缀树(多串SAM)的总结
之前我们给的SAM的例题,基本上是一个串建SAM的就能做的 如果要建多个串的SAM应该怎么做呢 首先看题,bzoj2780 我一开始的想法是SA以前的弄法,把串拼起来,中间加分隔符做SAM 这题确实可 ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线
传送门 朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r ...
- CF666E-Forensic Examination【广义SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/CF666E 解题思路 给出一个串\(S\)和\(n\)个串\(T_i\).\(m\)次询问\(S_{a\sim b} ...
- CF204E-Little Elephant and Strings【广义SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/CF204E 题目大意 \(n\)个字符串的一个字符串集合,对于每个字符串求有多少个子串是这个字符串集合中至少\(k\ ...
随机推荐
- Java数据结构与算法(2):栈
栈是一种线性表,特点在于它只能在一个位置上进行插入和删除,该位置是表的末端,叫做栈的顶(top).因此栈是后进先出的(FIFO).栈的基本操作有push.peek.pop. 栈的示意图 进栈和出栈都只 ...
- SpringBoot:使用IDEA快速构建项目
西部开源-秦疆老师:基于SpringBoot 2.1.6 的博客教程 秦老师交流Q群号: 664386224 未授权禁止转载!编辑不易 , 转发请注明出处!防君子不防小人,共勉! SpringBoot ...
- java中 使用输入+输出流对对象序列化
对象: 注意记得实现 Serializable package com.nf147.sim.entity; import java.io.Serializable; public class News ...
- React-Native 之 GD (十四)小时风云榜 及 当前时间操作 及 上一小时、下一小时功能实现
1.小时风云榜 GDHourList.js /** * 小时风云榜 */ import React, { Component } from 'react'; import { StyleSheet, ...
- Uncaught TypeError: Cannot read property 'length' of null错误怎么处理?
Uncaught TypeError: Cannot read property 'length' of null 错误怎么处理? 1.可能是返回的datagrid数据格式有问题,比如{"t ...
- Tarjan算法整理
众所周知,tarjan是个非常nb的人,他发明了很多nb的算法,tarjan算法就是其中一个,它常用于求解强连通分量,割点和桥等.虽然具体实现的细节不太一样,但是大体思路是差不多的.先来说一下大体思路 ...
- qbzt day3 晚上 平衡树的一些思想
pks大佬的blog 二叉查找树 任何一个节点左子树的所有元素都小于这个节点,右子树的所有元素都大于这个节点 查找一个节点:从根节点开始,比他小就向左走,比他大就向右走 平衡树:解决二叉查找树的一些痛 ...
- 浅释Functor、Applicative与Monad
引言 转入Scala一段时间以来,理解Functor.Applicative和Monad等概念,一直是我感到头疼的部分.虽然读过<Functors, Applicatives, And Mona ...
- Linux_LAMP 最强大的动态网站解决方案
目录 目录 LAMP Install LAMP via YUM Install LAMP via ResourceCode Apache Apache Virtual Machine Type Use ...
- Mac 10.14 下为php 安装xdebug 并让vscode支持
安装Xdebug 寻找对应php版本的xdebug版本 先将info输出到一个文件 php -i > info.txt 打开info.txt 复制所有内容 打开寻找合适xdebug的页面http ...