思路
这道题我们可以把每个单词正过来放在一个字典树里。
而我们把每个单词反过来,给每个单词单独建立一个字典树。
而询问要求的就是前缀在正串的字典树上的那个节点为根的子树中,所有串的反串字典树合并之后的那个字典树上,后缀的那个节点所对应的子树当中有多少个串就是答案。
举个小例子:
现在有 \(n\) 个单词。
分别是:“abc”,“ab”,“bac”,“bab”和“cbc”。
正串的字典树为:
反串的若干个字典树:
如果我们这时候要查询前缀为a,后缀为c的词有多少个,那么我们先去正串的字典树上找到对应的点。
然后我们把他子树内反串的字典树合并。
然后走后缀为c,发现他的子树中就只有一个串,答案就是一。
核心代码:
inline ll insf(string &s) {reverse(s.begin(), s.end());ll p = ++ftot;ll rt = p;sz[ftot] = 1;for (auto i : s) {p = ft[p][i - 'a'] = ++ftot, sz[ftot] = 1;}reverse(s.begin(), s.end());return rt;
}
inline ll merg(ll a, ll b) {if (!a) {return b;}if (!b) {return a;}sz[a] += sz[b];rep(i, 0, 25) {ft[a][i] = merg(ft[a][i], ft[b][i]);}return a;
}
string s[N];
struct node {string ht;
} ask[N];
ll ans[N];
inline void addtag(string &s, ll id) {ll p = 0;for (auto i : s) {ll v = i - 'a';if (!t[p][v]) {return ;}p = t[p][v];}g[p].push_back(id);
}
inline ll query(ll p, string &s) {for (auto i : s) {ll v = i - 'a';if (!ft[p][v]) {return 0;}p = ft[p][v];}return sz[p];
}
ll dd[N];
inline void dfs(ll x) {if (!wei[x]) {dd[x] = ++ftot;} else {dd[x] = rt[wei[x]];}rep(i, 0, 25) {if (t[x][i]) {dfs(t[x][i]);merg(dd[x], dd[t[x][i]]);}}
// cout << x << " ";for (auto v : g[x]) {
// cout << v << " ";ans[v] = query(dd[x], ask[v].ht);}
// cout << "\n";
}