【洛谷P6257】First of Her Name
题目
题目链接:https://www.luogu.com.cn/problem/P6257
众所周知,皇室家族的名字非常有讲究。而作为研究皇室的历史学家的你,最近接到了一个艰巨的任务——分析王国历史中所有皇室夫人的名字。
王国历史上有 \(n\) 位皇室夫人,方便起见,我们将其从 \(1\) 至 \(n\) 编号。除了 \(1\) 号夫人外,其余夫人的名字均为一个大写字母连接着她母亲的名字。而 \(1\) 号夫人作为王国的首任王后,她的名字只有一个大写字母。
例如,由于 AENERYS
由 A
与 ENERYS
组成,因此 ENERYS
是 AENERYS
的母亲。相似地,AENERYS
是 DAENERYS
与 YAENERYS
的母亲。
你知道王国历史上所有皇室夫人的姓名与关系,而你需要完成的任务是,对于其他历史学家感兴趣的名字串 \(s\),总共有多少位夫人的名字是以 \(s\) 起始的。
例如在样例的皇室族谱中,S
至 AENERYS
的这一支(包含 YS
、RYS
、ERYS
、NERYS
与 ENERYS
这几位夫人)均只有一位女儿。接下来 AENERYS
有两位女儿,分别是 DAENERYS
,以及女儿是 RYAENERYS
的 YAENERYS
。
在这个皇室家族内,有两位夫人的名字以 RY
起始,她们是 RYS
与 RYAENERYS
。而 ERYS
与 ENERYS
均以 E
起始。名字以 N
起始的仅有一位夫人 NERYS
。同样地,以 S
起始的仅有首位王后 S
。而没有任何一位夫人的名字以 AY
起始。
思路
将每一个询问串反过来,问题转化为求每一个询问串是多少个人名字的后缀。
将所有询问串扔进一个 AC 自动机内,建出 fail 树,然后按照人名的 Trie 找到每一个人的名字在 AC 自动机上能匹配到的最长的串对应节点,将这一个节点的 tag 加一。显然这个人能贡献的询问串是打 tag 的点 fail 链上的所有询问。
然后在 fail 树上做前缀和即可。为了保证计算到一个点时这个点子树内的贡献全部已经加上,我们可以按深度来枚举点。
时间复杂度 \(O(n+m+\sum |S|)\)。
代码
#include <bits/stdc++.h>
#define mp make_pair
using namespace std;
const int N=1000010;
int n,m,tot,head[N],ans[N];
char c[N],s[N];
struct edge
{
int next,to;
}e[N];
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
struct ACA
{
int tot,ch[N][26],fail[N],cnt[N];
vector<int> id[N],dep[N];
void ins(char *s,int x)
{
int p=0,len=strlen(s+1);
for (int i=len;i>=1;i--)
{
if (!ch[p][s[i]-'A'])
{
ch[p][s[i]-'A']=++tot;
dep[len-i+2].push_back(tot);
}
p=ch[p][s[i]-'A'];
}
id[p].push_back(x);
}
void build()
{
queue<int> q;
for (int i=0;i<26;i++)
if (ch[0][i]) q.push(ch[0][i]);
while (q.size())
{
int u=q.front(); q.pop();
for (int i=0;i<26;i++)
if (ch[u][i]) fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
else ch[u][i]=ch[fail[u]][i];
}
}
void query(int x)
{
for (int i=1000001;i>=1;i--)
for (int j=0;j<dep[i].size();j++)
{
int p=dep[i][j];
for (int k=0;k<id[p].size();k++)
ans[id[p][k]]=cnt[p];
cnt[fail[p]]+=cnt[p];
}
}
}AC;
void dfs(int x,int p)
{
AC.cnt[p]++;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
dfs(v,AC.ch[p][c[v]-'A']);
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1,x;i<=n;i++)
{
while (c[i]=getchar())
if (c[i]>='A' && c[i]<='Z') break;
scanf("%d",&x);
if (x) add(x,i);
}
for (int i=1;i<=m;i++)
{
scanf("%s",s+1);
AC.ins(s,i);
}
AC.build();
dfs(1,AC.ch[0][c[1]-'A']);
AC.query(0);
for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}