【题目描述】
萌萌买了一颗字符串树的种子,春天种下去以后夏天就能长出一棵很大的字符串树。字符串树很奇特,树枝上都密密麻麻写满了字符串,看上去很复杂的样子。
字符串树本质上还是一棵树,即N个节点N-1条边的连通无向无环图,节点从1到N编号。与普通的树不同的是,树上的每条边都对应了一个字符串。萌萌和JYY在树下玩的时候,萌萌决定考一考JYY。每次萌萌都写出一个字符串S和两个节点U,V,需要JYY立即回答U和V之间的最短路径(即,之间边数最少的路径。由于给定的是一棵树,这样的路径是唯一的)上有多少个字符串以为前缀。
JYY虽然精通编程,但对字符串处理却不在行。所以他请你帮他解决萌萌的难题。

【输入格式】
输入第一行包含一个整数N,代表字符串树的节点数量。
接下来N-1行,每行先是两个数U,V,然后是一个字符串S,表示节点和U节点V之间有一条直接相连的边,这条边上的字符串是S。输入数据保证给出的是一棵合法的树。
接下来一行包含一个整数Q,表示萌萌的问题数。
接来下Q行,每行先是两个数U,V,然后是一个字符串S,表示萌萌的一个问题是节点U和节点V之间的最短路径上有多少字符串以S为前缀。

【输出格式】
输出Q行,每行对应萌萌的一个问题的答案。

题解
前置知识点: 可持久化Trie 树链剖分/LCA

可持久化Trie支持查找一段区间内的所有字符串的相关信息
在此题中可以树链剖分后查询路径上的字符串有多少个包含有询问的前缀
码量稍大 其实就是道模板题 时间复杂度$O(q\ log\ n)$

也可以直接用LCA做 免去了树剖 建Trie树时令以当前节点i代表的树从历史版本fa[i]转移过来 然后询问x~y的路径就用 树x + 树y - 树lca 就行了 实现更简单一点

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <bits/stdc++.h>
using namespace std;

inline int read() {
int x = 0, f = 1; char ch = getchar();
for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1;
for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
return x * f;
}

int n, m, tot;
int head[100005], pre[200005], to[200005], len[200005], sz;
char s[200005][11], q[11];

inline int o(char ch) { return ch - 'a' + 1; }

inline void addedge(int u, int v) {
pre[++sz] = head[u]; head[u] = sz; to[sz] = v;
pre[++sz] = head[v]; head[v] = sz; to[sz] = u;
}

namespace Trie{
struct trie{
int son[30], sz;
} tr[5000005];
int rt[100005];

void insert(int d, int &ind, int lst, char *str) {
ind = ++tot; tr[ind].sz = tr[lst].sz;
if (str[d] < 'a' || str[d] > 'z') {
tr[ind].sz++;
return;
}
for (int i = 1; i <= 26; i++) {
tr[ind].son[i] = tr[lst].son[i];
}
insert(d+1, tr[ind].son[o(str[d])], tr[lst].son[o(str[d])], str);
for (int i = 1; i <= 26; i++) {
tr[ind].sz += tr[tr[ind].son[i]].sz - tr[tr[lst].son[i]].sz;
}
}

int query(int d, int lind, int rind, char *str) {
if (str[d] < 'a' || str[d] > 'z') {
return max(0, tr[rind].sz - tr[lind].sz);
} else return query(d+1, tr[lind].son[o(str[d])], tr[rind].son[o(str[d])], str);
}
}

using namespace Trie;

namespace Treechains{
int d[100005], dfn[100005], rnk[100005], tme, fa[100005], top[100005], siz[100005], son[100005], sonind[100005];

void dfs(int x, int fafa) {
siz[x] = 1;
for (int i = head[x]; i; i = pre[i]) {
int y = to[i]; if (y == fafa) continue;
d[y] = d[x] + 1; fa[y] = x;
dfs(y, x); siz[x] += siz[y];
if (!son[x] || siz[y] > siz[son[x]]) son[x] = y, sonind[x] = i;
}
}

void dfs2(int x, int tp) {
dfn[x] = ++tme; rnk[tme] = dfn[x]; top[x] = tp;
if (son[x]) {
insert(1, rt[tme+1], rt[tme], s[sonind[x]]);
dfs2(son[x], tp);
}
for (int i = head[x]; i; i = pre[i]) {
int y = to[i]; if (y == fa[x] || y == son[x]) continue;
insert(1, rt[tme+1], rt[tme], s[i]);
dfs2(y, y);
}
}

inline int query_path(int x, int y) {
int ret = 0;
while (top[x] != top[y]) {
if (d[top[x]] < d[top[y]]) swap(x, y);
ret += query(1, rt[dfn[top[x]]-1], rt[dfn[x]], q);
x = fa[top[x]];
}
if (d[x] > d[y]) swap(x, y);
ret += query(1, rt[dfn[x]], rt[dfn[y]], q);
return ret;
}
}

using namespace Treechains;

int main() {
n = read();
for (int i = 1; i < n; i++) {
addedge(read(), read());
scanf("%s", s[sz-1]+1); len[sz-1] = len[sz] = strlen(s[sz-1]+1);
for (int j = 1; j <= len[sz-1]; j++) s[sz][j] = s[sz-1][j];
}
dfs(1, 0); dfs2(1, 1);
m = read();
for (int i = 1; i <= m; i++) {
int x = read(), y = read();
scanf("%s", q+1);
query_path(x, y);
printf("%d\n", query_path(x, y));
}
return 0;
}

评论