题目传送门

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+树链的并的更多相关文章

  1. 51nod1600-Simple KMP【SAM,树链剖分】

    正题 题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1600 题目大意 给出一个字符串\(s\),每次在最后插入一个字符后求它的 ...

  2. 【bzoj5084】hashit 广义后缀自动机+树链的并+STL-set

    题目描述 你有一个字符串S,一开始为空串,要求支持两种操作 在S后面加入字母C 删除S最后一个字母 问每次操作后S有多少个两两不同的连续子串 输入 一行一个字符串Q,表示对S的操作 如果第i个字母是小 ...

  3. 【bzoj5084】 hashit(广义SAM+set)

    题面 传送门 题解 后缀平衡树是个啥啊我不会啊-- 那么我们来考虑一下\(SAM\)的做法好了 不难发现它的本义是要我们维护一棵\(trie\)树,并求出\(trie\)树上每一个节点到根的这段串的不 ...

  4. CF G. Indie Album 广义后缀自动机+树链剖分+线段树合并

    这里给出一个后缀自动机的做法. 假设每次询问 $t$ 在所有 $s$ 中的出现次数,那么这是非常简单的: 直接对 $s$ 构建后缀自动机,随便维护一下 $endpos$ 大小就可以. 然而,想求 $t ...

  5. 关于广义后缀树(多串SAM)的总结

    之前我们给的SAM的例题,基本上是一个串建SAM的就能做的 如果要建多个串的SAM应该怎么做呢 首先看题,bzoj2780 我一开始的想法是SA以前的弄法,把串拼起来,中间加分隔符做SAM 这题确实可 ...

  6. 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree

    原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...

  7. CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线

    传送门 朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r ...

  8. CF666E-Forensic Examination【广义SAM,线段树合并】

    正题 题目链接:https://www.luogu.com.cn/problem/CF666E 解题思路 给出一个串\(S\)和\(n\)个串\(T_i\).\(m\)次询问\(S_{a\sim b} ...

  9. CF204E-Little Elephant and Strings【广义SAM,线段树合并】

    正题 题目链接:https://www.luogu.com.cn/problem/CF204E 题目大意 \(n\)个字符串的一个字符串集合,对于每个字符串求有多少个子串是这个字符串集合中至少\(k\ ...

随机推荐

  1. 【转】结构化日志类库 ---- Serilog库

    源地址:https://www.cnblogs.com/mq0036/p/8479956.html 解决异常: Invalid cast from 'System.String' to 'Serilo ...

  2. 《SQL Server 2012 T-SQL基础》读书笔记 - 2.单表查询

    Chapter 2 Single-Table Queries GROUP BY之后的阶段的操作对象就是组(可以把一组想象成很多行组成的)了,HAVING负责过滤掉一些组.分组后的COUNT(*)表示每 ...

  3. MySQL主从复制 报错处理

    基于GTID的主从复制: 跳过一个事务: SET @@session.gtid_next = '冲突的GTID号';BEGIN;COMMIT; SET gtid_next = 'AUTOMATIC';

  4. SQL优化案例

    8月9日对数据库进行优化 每日22:00开始跑批 经过优化,跑批时间缩短 逻辑读对比: 20180808逻辑读 20180809逻辑读 优化前后CPU负载对比:

  5. python curl_get-pip.py Installing with get-pip.py

    w curl https://bootstrap.pypa.io/get-pip.py > curl_get-pip.pypython curl_get-pip.py https://pip.p ...

  6. AtomicIntegerArray 源码分析

    AtomicIntegerArray AtomicIntegerArray 能解决什么问题?什么时候使用 AtomicIntegerArray? 可以用原子方式更新其元素的 int 数组 如何使用 A ...

  7. 通过git新增、更新代码内容到github

    github可用于个人用户托管公开项目,对于异地上传下载十分方便 1.  准备工作 2.  首次上传执行命令集合 3.  更新执行命令集合 4.  命令总结 1.准备工作 a.注册github帐号 , ...

  8. Components controls 区别

    http://www.cnblogs.com/del/archive/2008/10/23/1317862.html 一个容器控件如果被其他控件指定为属主(Owner), 那么它的 Component ...

  9. [转载]借助openssl解析ECC公钥

    void GetPubKey(const char* FilePath, char* PubKey) { unsigned ]; unsigned char *pTmp = NULL; FILE *f ...

  10. day18 时间:time:,日历:calendar,可以运算的时间:datatime,系统:sys, 操作系统:os,系统路径操作:os.path,跨文件夹移动文件,递归删除的思路,递归遍历打印目标路径中所有的txt文件,项目开发周期

    复习 ''' 1.跨文件夹导包 - 不用考虑包的情况下直接导入文件夹(包)下的具体模块 2.__name__: py自执行 '__main__' | py被导入执行 '模块名' 3.包:一系列模块的集 ...