P4947 PION后缀自动机

题目传送门

  • 给出一棵 \(n\) 个点的树,每个点上有若干字符串,有 \(m\) 次操作,分为三种:

    • \(\texttt{query /p }u\texttt{ }v\),查询 \(u,v\) 简单路径上的边数。

    • \(\texttt{query /e }u \texttt{ }v\texttt{ *.}s\),查询 \(u,v\) 简单路径上字符串 \(s\) 出现了几次。

    • \(\texttt{del /e }u \texttt{ }v\texttt{ *.}s\),查询 \(u,v\) 简单路径上字符串 \(s\) 出现了几次,并删除这些 \(s\)

  • \(n,m\le 10^5\),字符串总数 \(k\) 不超过 \(5\times 10^5\),字符串长度不超过 \(8\)

\(\texttt{query /p}\) 操作比较简单,答案为 \(dep_u+dep_v-2\times dep_{\text{LCA}(u,v)}\)。考虑解决剩下两个操作。

字符串的种数很少,考虑使用 P5838 的套路,有两种方法:

  • 【方法一】

    对每种字符串维护一棵动态开点线段树,线段树的节点维护对应 \(dfn\) 序区间内该字符串的出现次数。

    • 对于 \(\texttt{query /e}\) 操作,跳链时在对应的线段树上查询当前重链的贡献。

    • 对于 \(\texttt{del /e}\) 操作,则先做一遍 \(\texttt{query /e}\) 操作,然后再进行删除。删除的时候,在跳链时将当前重链对应的 \(dfn\) 区间推平成 \(0\)

    时间复杂度为 \(\mathcal{O}(m\log^2 n)\),空间复杂度为 \(\mathcal{O}(k\log n)\)

  • 【方法二】

    对每种字符串维护一棵平衡树(这里使用 __gnu_pbds::tree),有序存放有该字符的点的 \(dfn\) 序,注意可能会重复,所以元素类型为 pairsecond 的作用是区分同一个点的两个相同字符串。

    • 对于 \(\texttt{query /e}\) 操作,设当前在点 \(u\),跳链时使用 order_of_key 找到在 \([1,dfn_{top_u})\)\([1,dfn_u]\) 中的值个数,相减即为当前重链上该字符串的出现次数。

    • 对于 \(\texttt{del /e}\) 操作,同样先做一遍 \(\texttt{query /e}\) 操作,然后再进行删除。删除的时候,使用 lower_bound 找到第一个大于等于 \(dfn_{top_u}\) 的迭代器 \(l\) 和第一个大于 \(dfn_u\) 的迭代器 \(r\)暴力删除 \([l,r)\) 之间的所有迭代器。

    看上去很暴力,但是一开始插入了 \(k\) 个元素,一个元素只能被删除一次,因此时间复杂度为 \(\mathcal{O}(m\log^2 n)\),空间复杂度为 \(\mathcal{O}(k+n)\)

给的是【方法二】的代码。笔者因为看到 P7735 的一种新写法,即求出 \(\texttt{LCA}(u,v)\) 后分别算 \(u\rightsquigarrow \text{LCA}(u,v)\)\(v\rightsquigarrow \text{LCA}(u,v)\) 的信息,以为会更好写,结果那种写法会算重 \(\text{LCA}(u,v)\) 的信息。在 P7735 中单点是没有贡献的,但是这题有,于是调了很久才发现要单独减去 \(\text{LCA}(u,v)\) 的贡献。因此部分代码不建议读者参考,还是建议读者去参考更常见的跳链查询

评测记录

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#define G __gnu_pbds
#define TOSNU tree_order_statistics_node_update
#define rnk order_of_key
#define lb lower_bound 
#define pii pair<int, int>
#define P make_pair 
#define fi first 
#define se second
#define vec vector
#define umap unordered_map
using namespace std; const int N = 1e5 + 5, inf = 1e9; typedef string str;
vec<int> g[N]; vector<pii> bin; vec<str> a[N]; str op1, op2, op3;
int n, m, dep[N], top[N], fa[N], hson[N], idx, dfn[N], siz[N], file;
umap<str, G::tree<pii, G::null_type, less<pii>, G::rb_tree_tag, G::TOSNU>> rbt;
void dfs1(int u) {
    siz[u] = 1;
    for (int v : g[u]) 
        if (v != fa[u]) dep[v] = dep[fa[v] = u] + 1, dfs1(v), siz[u] += siz[v];
}
void dfs2(int u) {
    for (int v : g[u]) {
        if (v == fa[u]) continue;
        if ((siz[v] << 1) > siz[u]) top[hson[u] = v] = top[u];
        else top[v] = v; dfs2(v);
    }
}
void dfs3(int u) {
    dfn[u] = ++idx; for (str s : a[u]) rbt[s].insert(P(idx, ++file));
    if (hson[u]) dfs3(hson[u]);
    for (int v : g[u]) if (v != fa[u] && v != hson[u]) dfs3(v);
}
int LCA(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}
int chain(int u, int v, str suf) {
    int ret = 0;
    while (top[u] != top[v]) {
        ret += rbt[suf].rnk(P(dfn[u], inf)) - rbt[suf].rnk(P(dfn[top[u]], 0));
        u = fa[top[u]];
    }
    return ret + rbt[suf].rnk(P(dfn[u], inf)) - rbt[suf].rnk(P(dfn[v], 0));
}
void erase(int u, int v, str suf) {
    while (top[u] != top[v]) {
        auto l = rbt[suf].lb(P(dfn[top[u]], 0)), r = rbt[suf].lb(P(dfn[u], inf)); 
        bin.clear(); for (auto it = l; it != r; ++it) bin.emplace_back(*it);
        for (pii i : bin) rbt[suf].erase(i); u = fa[top[u]];
    }
    auto l = rbt[suf].lb(P(dfn[v],0)), r = rbt[suf].lb(P(dfn[u], inf)); 
    bin.clear(); for (auto it = l; it != r; ++it) bin.emplace_back(*it);
    for (pii i : bin) rbt[suf].erase(i);
}
int query(int u, int v, int lca, str suf) {
    if (lca == u) return chain(v, u, suf);
    if (lca == v) return chain(u, v, suf);
    return chain(u, lca, suf) + chain(v, lca, suf) - chain(lca, lca, suf);
}
void del(int u, int v, int lca, str suf) { 
    if (lca == u) return erase(v, u, suf), void();
    if (lca == v) return erase(u, v, suf), void();
    erase(u, lca, suf), erase(v, lca, suf);
}
signed main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0); cin >> n >> m;
    for (int i = 1, u, v; i < n; ++i)
        cin >> u >> v, g[u].emplace_back(v), g[v].emplace_back(u);
    for (int i = 1, x; i <= n; ++i) {
        cin >> x; 
        for (str tmp; x--;) cin >> tmp, a[i].emplace_back(tmp);
    }
    dfs1(1), dfs2(top[1] = 1), dfs3(1);
    for (int i = 1, u, v, lca; i <= m; ++i) {
        cin >> op1 >> op2 >> u >> v; lca = LCA(u, v);
        if (op2 == "/p")
            cout << dep[u] + dep[v] - (dep[lca] << 1) << '\n';
        else {
            cin >> op3; op3 = op3.substr(2);
            cout << query(u, v, lca, op3) << '\n';
            if (op1 == "del") del(u, v, lca, op3);
        }
    }
    return 0;
}
posted @ 2023-09-15 09:52  lzyqwq  阅读(31)  评论(0)    收藏  举报