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;
}

原文地址:https://www.cnblogs.com/hankeke/p/bzoj5084.html

时间: 2024-07-31 19:08:57

bzoj5084 hashit 广义SAM+树链的并的相关文章

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

之前我们给的SAM的例题,基本上是一个串建SAM的就能做的 如果要建多个串的SAM应该怎么做呢 首先看题,bzoj2780 我一开始的想法是SA以前的弄法,把串拼起来,中间加分隔符做SAM 这题确实可以这么做,这样根据SAM能识别所有子串的性质 而且每个节点都代表了唯一的一个串 每个询问串我们都能找到最终转移到哪(找不到就是没出现过) 问在多少个串出现过这就等价于在ST(s)的parent树的子树中,出现了多少种不同的权值 这显然可以维护dfs序,用经典的离线做法来搞 1 type node=r

关于SAM和广义SAM

关于SAM和广义SAM 不是教程 某些思考先记下来 SAM 终于学会了这个东西诶...... 一部分重要性质 确定一个重要事情,S构造出的SAM的一个重要性质是当且仅当对于S的任意一个后缀,可以从1号节点走到终止状态.专业的名词叫做有限状态自动机. trans[st][c]表示的是对于状态st,如果将st中任意串s加一个c,那么会到达的新状态new,显然new是唯一的.假如不唯一那么s一定不属于同一个st. fa[st]表示的是对于状态st,如果慢慢缩小st中后缀长度,会到达的第一个状态.规定一

【BZOJ 3926】 [Zjoi2015]诸神眷顾的幻想乡 (广义SAM)

3926: [Zjoi2015]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 974  Solved: 573 Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n块空地.在过去,幽香为了方便,在这n块空地之间修建

bzoj 4196 树链剖分 模板

[Noi2015]软件包管理器 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 2135  Solved: 1232[Submit][Status][Discuss] Description Linux用户和OSX用户一定对软件包管理器不会陌生.通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个软件包的安装所依赖的其它软件包),完成所有的配置.Debian/Ubu

luogu3346 诸神眷顾的幻想乡 (广义SAM)

首先,让每一个叶节点做一次树根的话,每个路径一定至少有一次会变成直上直下的 于是对于每个叶节点作为根产生的20个trie树,把它们建到同一个广义SAM里 建法是对每个trie dfs去建,last就是父亲的那个节点:每次做一个新trie时,last给成root 然后答案就是每个节点表示的长度和 1 #include<bits/stdc++.h> 2 #define pa pair<int,int> 3 #define CLR(a,x) memset(a,x,sizeof(a)) 4

CF gym 100962D Deep Purple [后缀树,树链剖分,线段树]

Codeforces 思路 感觉这个离线的思路好神仙啊qwq 对于每个询问\([l,r]\)其实就是要求\(p_{max}\),使得\(lcs(s[1,p],s[1,r])>p-l\),也就是\(lcs(s[1,p],s[1,r])+l>p\). 首先把询问离线按\(r\)排序,然后从右往左扫,每次 处理之前已经被加进去的询问,看当前位置是否能被作为\(p\),然后把已经处理完毕的询问给删掉. 把当前询问塞进去. 建出反串的后缀树,那么不等式左边就是\(dep_{lca(p,r)}+l\).

CF452E Three strings【广义SAM】

传送门 也是广义 SAM 的板子题,建好广义 SAM,统计 \(epA,epB,epC\),然后对于区间 \(ans[len[fa[x]]+1],...,ans[len[x]]\) 加上 \(epA\times epB\times epC\) 就行了,当然这个用差分实现简单快捷. #include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=3e5+10; const int mod=1e9+7;

luoguP2590 [ZJOI2008]树的统计 [树链剖分] [TLE的LCT]

题目描述 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 输入输出格式 输入格式: 输入文件的第一行为一个整数n,表示节点的个数. 接下来n – 1行,每行2个整数a和b,表示节点a和节点b之

BZOJ 2243: [SDOI2011]染色 树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1886  Solved: 752[Submit][Status] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. In