Codeforces Bubble Cup 11 J. Moonwalk challenge加强版
题意
有一棵 \(n\) 个节点的树,每条边上有一个字符,有 \(m\) 次询问。
每次会选定两个点 \(u, v\) , \(u\) 到 \(v\) 的路径上的字符形成了一个字符串 \(T\) ,再选定一个字符串 \(S\) ,计算 \(S\) 在 \(T\) 中的出现次数。
\(n, m \le 10^5, \sum |S| \le 5 \times 10^6\)
题解
原题多了一个保证 \(|S| \le 100\) 那么信息不会很多,直接倍增即可保存所有信息,复杂度是 \(𝑂(m|S| + n|S| \log(n|S|) + m \log n(\log n + |S|))\) 。
显然对于 \(S\) 的长度限制是没有必要的。
询问一个串在另外一个串中出现的次数,查询串是给你的,但每次询问的时候不好把母串放进来跑,那么通常可以利用 \(SA/SAM\) 解决。
然后我们考虑是询问路径,我们进行树链剖分。那么我们只需要对于每个剖分的链,只需要询问这个串在其中一段出现多少次即可。
这可以用 \(SA\) + 主席树或者 \(SAM\) + 二维数点实现,都是单次询问 \(O(\log n)\) 的。
然后我们需要考虑跨轻边的部分如何计算,这一部分可以每次花 \(O(|S|)\) 的代价做 \(\mathrm{KMP}\) 匹配。
那么 \(SAM\) 做就是 \(O(n|\Sigma| + (m \log n + \sum |S|) \log n)\) ,\(SA\) 为 \(O(n \log n + (m \log n+ \sum |S|)\log n)\) 。
其实还可以做到更优秀,我们对于整颗树建一颗广义 \(SAM\) 。(但是此处实现会有很多问题)
其中一种也许可行的实现方式是在 \(DFS\) 每次插入一个字母的时候,从父亲处继承 \(Last\) ,然后直接插入,这是本人的实现方式。
但他们说这样不仅复杂度可能会被卡到 \(O(n \sqrt n)\) 并且还有可能有正确性问题。
正确的建法是 \(BFS\) 逐层添加字符,这样就是复杂度和正确性都是正确的。
这样建完后,我们每次相当于查询整个 \(fail\) 树上上的一个子树在另外一颗数上 \(dfn\) 的一段连续区间。
那么这个用主席树合并实现就可以避免离线的细节了。
然后还有一个细节,有可能有些计算进去的串向上延伸会超过 \(lca\) ,那么一开始查询的时候限制一下它向上延伸的高度即可,此处树剖没有那么好实现,利用倍增即可。
这样的话,我们只需要在 \(lca\) 处计算跨链的贡献即可。
复杂度就优化成了 \(O(n|\Sigma| + n\log n + m \log^2 n + \sum |S|)\) 。
由于信息可减,查询链上信息。其实可以利用括号序列把复杂度优化成 \(O(n|\Sigma| + n\log n + m \log n + \sum |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;
}