Codeforces Bubble Cup 11 J. Moonwalk challenge加强版

题意

有一棵 n 个节点的树,每条边上有一个字符,有 m 次询问。

每次会选定两个点 u,vuv 的路径上的字符形成了一个字符串 T ,再选定一个字符串 S ,计算 ST 中的出现次数。

n,m105,|S|5×106

题解

原题多了一个保证 |S|100 那么信息不会很多,直接倍增即可保存所有信息,复杂度是 𝑂(m|S|+n|S|log(n|S|)+mlogn(logn+|S|))

显然对于 S 的长度限制是没有必要的。

询问一个串在另外一个串中出现的次数,查询串是给你的,但每次询问的时候不好把母串放进来跑,那么通常可以利用 SA/SAM 解决。

然后我们考虑是询问路径,我们进行树链剖分。那么我们只需要对于每个剖分的链,只需要询问这个串在其中一段出现多少次即可。

这可以用 SA + 主席树或者 SAM + 二维数点实现,都是单次询问 O(logn) 的。

然后我们需要考虑跨轻边的部分如何计算,这一部分可以每次花 O(|S|) 的代价做 KMP 匹配。

那么 SAM 做就是 O(n|Σ|+(mlogn+|S|)logn)SAO(nlogn+(mlogn+|S|)logn)

其实还可以做到更优秀,我们对于整颗树建一颗广义 SAM 。(但是此处实现会有很多问题)

其中一种也许可行的实现方式是在 DFS 每次插入一个字母的时候,从父亲处继承 Last ,然后直接插入,这是本人的实现方式。

但他们说这样不仅复杂度可能会被卡到 O(nn) 并且还有可能有正确性问题。

正确的建法是 BFS 逐层添加字符,这样就是复杂度和正确性都是正确的。

这样建完后,我们每次相当于查询整个 fail 树上上的一个子树在另外一颗数上 dfn 的一段连续区间。

那么这个用主席树合并实现就可以避免离线的细节了。

然后还有一个细节,有可能有些计算进去的串向上延伸会超过 lca ,那么一开始查询的时候限制一下它向上延伸的高度即可,此处树剖没有那么好实现,利用倍增即可。

这样的话,我们只需要在 lca 处计算跨链的贡献即可。

复杂度就优化成了 O(n|Σ|+nlogn+mlog2n+|S|)

由于信息可减,查询链上信息。其实可以利用括号序列把复杂度优化成 O(n|Σ|+nlogn+mlogn+|S|)

代码

SAM 处可能会存在问题,仅供参考。

最后就是 广义SAM + 主席树合并 + 树链剖分 + 倍增 + KMP 即可。

写了 6.7KB 。。。想死了。。。

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define pb push_back using namespace std; typedef unsigned long long ull; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } inline char read_char() { char ch(getchar()); for (; !islower(ch); ch = getchar()); return ch; } void File() { freopen ("e.in", "r", stdin); freopen ("e.out", "w", stdout); } const int N = 1e5 + 1e3, M = N << 1; #define mid ((l + r) >> 1) #define lson ls[o], l, mid #define rson rs[o], mid + 1, r template<int Maxn> struct President_Tree { int ls[Maxn], rs[Maxn], sumv[Maxn], Size; void Update(int &o, int l, int r, int up) { if (!o) o = ++ Size; if (l == r) { ++ sumv[o]; return ; } up <= mid ? Update(lson, up) : Update(rson, up); sumv[o] = sumv[ls[o]] + sumv[rs[o]]; } int Query(int o, int l, int r, int ql, int qr) { if (!o) return 0; if (ql <= l && r <= qr) return sumv[o]; if (qr <= mid) return Query(lson, ql, qr); if (ql > mid) return Query(rson, ql, qr); return Query(lson, ql, qr) + Query(rson, ql, qr); } int Merge(int x, int y) { if (!x || !y) return x | y; int o = ++ Size; ls[o] = Merge(ls[x], ls[y]); rs[o] = Merge(rs[x], rs[y]); sumv[o] = sumv[x] + sumv[y]; return o; } }; President_Tree<N * 120> T; int n; template<int Maxn, int Alpha> struct Suffix_Automaton { int trans[Maxn][Alpha], link[Maxn], len[Maxn], num[Maxn], Size; Suffix_Automaton() { Size = 1; } inline int Extend(int Last, int id, int pos) { int cur = ++ Size, p = Last; len[cur] = len[Last] + 1; num[cur] = pos; for (; p && !trans[p][id]; p = link[p]) trans[p][id] = cur; if (!p) link[cur] = 1; else { int q = trans[p][id]; if (len[q] == len[p] + 1) link[cur] = q; else { int clone = ++ Size; Cpy(trans[clone], trans[q]); len[clone] = len[p] + 1; link[clone] = link[q]; for (; p && trans[p][id] == q; p = link[p]) trans[p][id] = clone; link[cur] = link[q] = clone; } } return cur; } vector<int> G[Maxn]; int rt[Maxn]; void Dfs(int u) { for (int v : G[u]) Dfs(v), rt[u] = T.Merge(rt[u], rt[v]); } void Build() { For (i, 2, Size) { G[link[i]].pb(i); if (num[i]) T.Update(rt[i], 1, n, num[i]); } Dfs(1); } int Trans(char *str) { int u = 1; For (i, 1, strlen(str + 1)) { int id = str[i] - 'a'; if (!trans[u][id]) return 0; u = trans[u][id]; } return u; } }; Suffix_Automaton<N * 2, 26> SAM; int Head[N], Next[M], to[M], val[M], e = 0; inline void add_edge(int u, int v, int w) { to[++ e] = v; Next[e] = Head[u]; Head[u] = e; val[e] = w; } int anc[N][20]; int sz[N], son[N], pid[N], fa[N], dep[N], ch[N]; void Dfs_Init(int u, int from = 0) { sz[u] = 1; dep[u] = dep[anc[u][0] = fa[u] = from] + 1; for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) if (v != from) { ch[v] = val[i]; Dfs_Init(v, u); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } int dfn[N], top[N]; void Dfs_Part(int u) { static int clk = 0; dfn[u] = ++ clk; if (u > 1) pid[u] = SAM.Extend(pid[fa[u]], ch[u], clk); top[u] = son[fa[u]] == u ? top[fa[u]] : u; if (son[u]) Dfs_Part(son[u]); for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) if (v != fa[u] && v != son[u]) Dfs_Part(v); } int Lca(int x, int y) { for (; top[x] != top[y]; x = fa[top[x]]) if (dep[top[x]] < dep[top[y]]) swap(x, y); return dep[x] < dep[y] ? x : y; } int Calc(int x, int y, int pos) { int res = 0; for (; top[x] != top[y]; x = fa[top[x]]) { int l = dfn[top[x]], r = dfn[x]; res += T.Query(SAM.rt[pos], 1, n, l, r); } return res + T.Query(SAM.rt[pos], 1, n, dfn[y], dfn[x]); } const int Len = 5e6 + 1e3; char str[Len]; int Log2[N]; struct KMP { void Get_Fail(char *S, int *fail) { For (i, 2, strlen(S + 1)) { int j = fail[i - 1]; while (j && S[j + 1] != S[i]) j = fail[j]; fail[i] = S[j + 1] == S[i] ? j + 1 : 0; } } int Match(char *S, char *T, int *fail) { int lenT = strlen(T + 1), j = 0, res = 0; For (i, 1, strlen(S + 1)) { while (j && T[j + 1] != S[i]) j = fail[j]; if (T[j + 1] == S[i]) ++ j; if (j == lenT) ++ res; } return res; } }; KMP FUCK; char S[Len], tmp[Len]; int fail[Len]; int MATCH(int x, int y) { int lca = Lca(x, y), len = 0, lt = 0; for (; x != lca; x = fa[x]) S[++ len] = ch[x] + 'a'; for (; y != lca; y = fa[y]) tmp[++ lt] = ch[y] + 'a'; reverse(tmp + 1, tmp + lt + 1); For (i, 1, lt) S[++ len] = tmp[i]; FUCK.Get_Fail(str, fail); S[len + 1] = '\0'; return FUCK.Match(S, str, fail); } int Ancestor(int x, int k) { Fordown (i, Log2[dep[x]], 0) if (dep[anc[x][i]] >= k) x = anc[x][i]; return x; } int Split(int x, int y, int lca) { int len = strlen(str + 1), gap = dep[lca] + (len - 1); if (len == 1) return 0; x = Ancestor(x, min(gap, dep[x])); y = Ancestor(y, min(gap, dep[y])); return MATCH(x, y); } int main () { File(); n = read(); For (i, 1, n - 1) { int u = read(), v = read(), ch = read_char() - 'a'; add_edge(u, v, ch); add_edge(v, u, ch); } pid[1] = 1; Dfs_Init(1); Dfs_Part(1); SAM.Build(); For (i, 2, n) Log2[i] = Log2[i >> 1] + 1; For (j, 1, Log2[n]) For (i, 1, n) anc[i][j] = anc[anc[i][j - 1]][j - 1]; int m = read(); For (i, 1, m) { int u = read(), v = read(); scanf ("%s", str + 1); int len = strlen(str + 1); int lca = Lca(u, v), ans = 0, dis = dep[u] + dep[v] - 2 * dep[lca] + 1; if (len >= dis) { puts("0"); continue; } int gap = dep[lca] + len; if (lca == u || lca == v) { if (v == lca) reverse(str + 1, str + len + 1); if (u == lca) swap(u, v); if (gap <= dep[u]) ans = Calc(u, Ancestor(u, gap), SAM.Trans(str)); } else { if (gap <= dep[v]) ans = Calc(v, Ancestor(v, gap), SAM.Trans(str)); reverse(str + 1, str + len + 1); if (gap <= dep[u]) ans += Calc(u, Ancestor(u, gap), SAM.Trans(str)); reverse(str + 1, str + len + 1); ans += Split(u, v, lca); } printf ("%d\n", ans); } #ifdef zjp_shadow freopen ("/proc/self/status", "r", stdin); char str[1010]; while (cin.getline(str, 1000)) cerr << str << endl; #endif return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10209360.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(471)  评论(2编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示