题目传送门

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. Linux下修改mysql root密码

    1.修改MySQL的配置文件(默认为/etc/my.cnf),在[mysqld]下添加一行skip-grant-tables 2.保存配置文件后,重启MySQL服务 service mysqld re ...

  2. Maps.newHashMap 和 new HashMap的区别

    区别: (1)Map<String, Object> result = new HashMap<String,Object>(); 这种是java原生API写法,需要你手动加泛 ...

  3. ijkplayer阅读笔记系列<转>

    http://blog.csdn.net/peckjerry/article/details/47663275

  4. ParaEngine 一个同事的公司的开源引擎

    看说明作者是李西峙,浙大研究生,靠投资研发此引擎,10年了,大概翻了下github里的文件,值得收藏,至少里面有voxelmesh https://github.com/LiXizhi/NPLRunt ...

  5. 20160520—JS打分控件

    效果预览: 可实现功能:鼠标在滑动条内左右滑动,文本框内分数变动:文本框输入数字,滑动条长度自动改变. JavaScript代码: $(function () { scoreFun($("# ...

  6. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_06 Properties集合_3_Properties集合中的方法load

    键值对文件,读取到集合中来使用 分隔符也可以用空格 读出来乱码

  7. free pascal

    https://freepascal.org/ free pascal OPEN SOURCE COMPILER FOR PASCAL AND OBJECT PASCAL GENERAL HomeNe ...

  8. delphi 语法 点滴总结clientdataset

    clientdataset   记录数 是从1 开始的. self.ClientDataSet5.RecNo   记录数 可以 读写 赋值. 随意改动. 哈哈 很方便.

  9. 删除历史日志的一个API

    删除历史日志的一个API bool DeleteOldFiles(const char* strFolder, const char* strPrefix, bool is_recursion, UI ...

  10. python程序的模块与包

    python的程序是由模块组成的,一个python文件就是一个模块,而模块一般由代码,函数,或者类组成.创建baiduHq.py模块(文件),在该模块中编写变量,函数,类,来说明在一个模块中,变量的输 ...