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\ ...
随机推荐
- VS2015中添加QT5.9.0插件
https://blog.csdn.net/hhhuang1991/article/details/79768595 VS2015里使用QTDIR路径查找QT开发包目录 路径配置操作系统环境变量里添加 ...
- 关于Java协变性的思考
简而言之,如果A IS-A B,那么A[] IS-A B[]. 举例:现在有类型Person.Employee和Student.Employee 是一个(IS-A) Person,Student是一个 ...
- ubuntu 安装java1.8
1.进入官网下载页面http://www.oracle.com/technetwork/java/javase/downloads/index.html 2.选择需要的版本,进入下载页面 下载 jdk ...
- Numpy的补充(重要!!)
轴的概念 英文解释 https://www.sharpsightlabs.com/blog/numpy-axes-explained/ 汉化解释 https://www.jianshu.com/p/ ...
- Python笔记(二十二)_魔法方法_基本魔法方法
__init__(self[,...]) __init__和__new__组成python的构造器,但__init__更多的是负责初始化操作,相当于一个项目中的配置文件,__new__才是真正的构造函 ...
- centos7安装配置jdk、tomcat
centos7安装jdk1.8 1.新建文件夹java,上传文件jdk-8u111-linux-x64.tar.gz到java文件夹 2.解压tar包: tar -zxvf jdk-8u111-lin ...
- 简述移动端与PC端的区别
1.移动端与PC端的区别 PC考虑的是浏览器的兼容性,而移动端开发考虑的更多的是手机兼容性,因为目前不管是android手机还是ios手机,一般浏览器使用的都是webkit内核,所以说做移动端开发,更 ...
- 解决django项目无法连接远程mysql的问题
我们都知道django项目可以通过修改settings.py文件中的DATABASES这个对象,使用不同的数据库. 如图所示,我们想连接远程的mysql,修改settings.py的配置 然后我们在终 ...
- MethodBase.GetCurrentMethod 方法
如果当前正在执行的方法定义泛型类型上MethodInfo返回GetCurrentMethod通过泛型类型定义 (即,MethodInfo.ContainsGenericParameters返回true ...
- Vue 基础 day05 webpack 3.x
什么是webpack webpack 是前端的一个项目构建工具, 它是基于 Node.js 开发出来的一个前端工具 借助于webpack这个前端自动化构建工具, 可以完美实现资源的合并.打包.压缩.混 ...