#字典树 #模拟 看到字符串的前缀累加和,果断写字典树。字典树,就是第 $i$ 层结点代表第 $i$ 个字符,而每个结点的父亲就是上一个字符,儿子就是下一个父亲的所有可能。 拿样例来举例。首先,我们的第一个字符有多种不同的可能,我们当然不能开 $26$ 棵字典树,而是拿额外一个没有用的结点来充当树根。注意我们的字典树需要记录每个点的点权,用来统计答案。然后我们加入 $\texttt{ab}$,那么首先第一个字符 $\texttt{a}$ 不存在,那么我们就额外新建一个结点;第二个字符 $b$ 也不存在,就再新建一个结点。将路上的每一个结点权值都加 $1$。 然后是第二个字符串 $\texttt{abc}$,由于 $\texttt{ab}$ 已经是第一个串的前缀,那么答案就需要加 $2$。最后一个字符 $\texttt{c}$ 在 $\texttt{ab}$ 的后面不存在,那么我们就新建一个结点。将路上的每一个结点权值都加 $1$。 最后是 $\texttt{arc}$。$\texttt{a}$ 这个前缀在之前出现了 $2$ 次,那么答案就需要加 $2$。然后 $\texttt{r}$ 和 $\texttt{c}$ 都不存在,那么就需要新建结点。将路上的每一个结点权值都加 $1$。 答案:$2+2=4$。 ```cpp #include <iostream> using namespace std; using LL = long long; const LL kMaxN = 3e5 + 5; LL tr[kMaxN], nxt[kMaxN][26], n, cnt = 1, ans; // cnt 初始化为 1,因为需要一个额外的根 string s; void add(const string &s) { // 加入字符串 s LL p = 1; for (char c : s) { // 遍历每一个字符 if (!nxt[p][c - 'a']) { // 如果不存在 p = nxt[p][c - 'a'] = ++cnt; // 新申请一个儿子 } else { // 已经存在 p = nxt[p][c - 'a']; // 直接跳过去 } ans += tr[p]; // 累加答案 tr[p]++; // 路径上的数加一 } } int main() { cin >> n; for (LL i = 1; i <= n; i++) { cin >> s; add(s); } cout << ans << '\n'; return 0; } ```