P6257 [ICPC 2019 WF] First of Her Name 题解
前言
- 能推的题 \(+1\)
\(solve\)
- 我们发现名字是向前接的,不好搞,我们让它向后接,即从第一代王后为开始向下建立 \(Trie\),好理解,不过还是画张图,模个样例

- 那我们要做的就是从任意一个节点开始到根的串,询问串是其前缀的个数
- 那么你就有一个 \(O(n^3)\) 的做法了
- 我们发现我们建这棵 \(Trie\) 是倒着建的,而我们要查前缀还要倒着遍历 \(Trie\),非常不美丽,那我们为啥不让询问串也反过来,这样就成询问从 \(Trie\) 根出发到任意节点形成的串的后缀了
- 给你文本串和模式串,分别求出每个模式串对于每个文本串中出现的次数,这你想到了啥,\(AC\) 自动机板子
- 你直接在 \(Trie\) 的节点上暴力匹配,每到一个节点,在 \(ACAM\) 上转移到其对应的节点,增加计数器,最后像板子一样直接跑 \(fail\) 树的 \(topo\) 就行了
点击查看代码
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
constexpr int maxn=1e6+10;
int n,m;
char s[maxn];
vector<int> e[maxn];
namespace ACAM
{
struct _ {int ch[26],fail,d,id;}t[maxn];
int tot,pid;
void BuildTrie(string s,int &id)
{
int x=0;
for (int i=0;i<s.size();i++)
{
int &son=t[x].ch[s[i]-'A'];
if (!son) son=++tot;
x=son;
}
if (!t[x].id) t[x].id=++pid;
id=t[x].id;
}
void BuildACAM()
{
queue<int> q;
for (int i=0;i<26;i++)
if (t[0].ch[i])
q.push(t[0].ch[i]);
while (!q.empty())
{
int x=q.front();q.pop();
for (int i=0;i<26;i++)
{
if (t[x].ch[i])
t[t[x].ch[i]].fail=t[t[x].fail].ch[i],
t[t[t[x].fail].ch[i]].d++,q.push(t[x].ch[i]);
else
t[x].ch[i]=t[t[x].fail].ch[i];
}
}
}
}using namespace ACAM;
int idx[maxn];
int cnt[maxn];
void dfs(int x,int fa,int p)
{
p=t[p].ch[s[x]-'A'];
cnt[p]++;
for (int y:e[x])
if (y!=fa)
dfs(y,x,p);
}
int ans[maxn];
void topo()
{
queue<int> q;
for (int i=0;i<=tot;i++)
if (!t[i].d)
q.push(i);
while (!q.empty())
{
int x=q.front();q.pop();
ans[t[x].id]=cnt[x];
int y=t[x].fail;
cnt[y]+=cnt[x];
if (!(--t[y].d))
q.push(y);
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin >> n >> m;
for (int i=1;i<=n;i++)
{
cin >> s[i];
int x;cin >> x;
e[x].pb(i),e[i].pb(x);
}
for (int i=1;i<=m;i++)
{
string k;
cin >> k;
reverse(k.begin(),k.end());
BuildTrie(k,idx[i]);
}
BuildACAM();
dfs(1,0,0);
topo();
for (int i=1;i<=m;i++)
cout << ans[idx[i]] << endl;
return 0;
}
$\mathscr{msjing}$

浙公网安备 33010602011771号