欧洲皇室女士的名字
在欧洲很多皇室中,女性是绝对的领导者,给皇室家族的女士起名字是非常重要的一件事情。Joi 是一位研究欧洲皇室的历史学家,现在分给她一份工作,分析皇室家族中女士名字起名规则。为了方便分析,现在假定一共有 \(n\) 位皇族女士,编号从 \(1\) 号到 \(n\) 号。每一位皇族女士的名字都按照一个大写字母与她母亲的名字连在一起起名字,例外的是 \(1\) 号皇族女士,她是皇族的创始人,名字只有一个大写字母。
例如: ENERYS 是 AENERYS 的母亲(因为 AENERYS 这个名字是由一个大写字母 'A' 和 'ENERYS' 连在一起组成的,ENERYS 是她母亲的名字)。同样,AENERYS是 DAENERYS 和 YAENERYS 的母亲;现在 Joi 知道所有皇族女士的名字,现在需要你编写一个程序帮助 Joi 确定某些给定的字符串 \(s\) 作为前缀的皇族女士的人数。
第一行包含两个整数 \(n\) 和 \(k\),\(n\) 表示皇家女士的总数,\(k\) 表示查询字符串的数量,以空格隔开。其中 \(1 \le n,k \le 10^6\)。
之后 \(n\) 行, 第 \(i\) 行用于描述第 \(i\) 位皇族女士。其中包含一个大写字母 \(c_i\)('A'–'Z')和一个整数 \(p_i\),\(c_i\) 表示她名字的第一个字母,\(p_i\) 表示她母亲的编号(对于创始人,\(p_1=0\)) 。其中 \(p_i<i\) ,保证所有名字唯一。
其余的 \(k\) 行每行包含一个非空查询字符串,仅由大写字母组成。查询字符串的长度总和最多为 \(10^6\)。
输出 \(k\) 行, 第 \(i\) 行包含以 \(i^{th}\) 查询字符串作为名字前缀的皇族女士的数量。
Input:
10 5
S 0
Y 1
R 2
E 3
N 4
E 5
A 6
D 7
Y 7
R 9
RY
E
N
S
AY
output:
2
2
1
1
0
皇族从创始人 S 到 RYAENERYS,后代起名字的过程是:S 的女儿是 YS,YS 的女儿是 RYS,RYS 的女儿是 ERYS,ERYS 的女儿是 NERYS,NERYS的女儿是 ENERYS,ENERYS 的女儿是 AENERYS(每位女士都正好有一个女儿);然后,AENERYS 有两个女儿 DAENERYS 和 YAENERYS,YAENERYS 有一个女儿 RYAENERYS。
在这个皇族家庭中,RY 作为名字前缀的有两个女士:RYS 和 RYAENERYS;E作为名字前缀的有两位女士:ERYS 和 ENERYS;N 作为名字前缀的有一位女士: NERYS;S 作为前缀的有一位:就是皇族创始人 S;AY 作为名字前缀的没有。
对于 \(25\%\)的测试点, \(1 \le n \le 100\),\(1 \le k \le 300\)。
对于 \(65\%\)的测试点, \(1 \le n \le 10^4\),\(1 \le k \le 6000\)。
对于 \(80\%\)的测试点, \(1 \le n \le 4\times10^4\),\(1 \le k \le 10^4\)。
对于 \(100\%\)的测试点,\(1 \le n \le 10^6\),\(1 \le k \le 10^6\)。
sol:
4884 欧洲皇室女士的名字(name)
1
题目大意:有 n 个字符串,每个字符串是特定的字符串前面再加上一个字符,有 k
组询问,每个求以每个询问开头的字符串个数。
首先很明显,这是一个字符串问题。但是每个字符串是某个特定的字符串前面加一个字符很不好处理,所以我们把它倒过来,变成在后方加一个字符,而询问也要倒过来,并且查询后缀。这样一来,问题稍微简单了一点。
我们发现,名字串的形状是像一棵树,所以我们可以用树的方式把名字表示出来。一个连续的串在这个树上出现的次数就是以这个串为后缀的名字的个数。
这是有多个查询并且有多个比对的字符串问题,所以考虑使用AC自动机,把询问丢进AC自动机中。
我们可以遍历名字树,看看在AC自动机中有没有相同的节点(父亲节点的字符也相同),并且记录这种节点的出现次数。
然后要把一个节点的fail节点出现次数加上这个节点的出现次数才是所有这个串的出现次数,枚举的时候记得按寻找fail节点的时候的队列顺序来,不然会漏记。
最后统计一下答案输出就好了。
2
解决此问题的方法至少有两种。 解决这一问题的大多数都采用了第一种方法, 即对皇室女士进行分类。 虽然单纯的排序可能需要 O(n2logn) 的时间, 但可以考虑构建后缀数组的方法以 O(nlogn) 的时间进行排序, 使用标准的内置排序而不是实现计数排序。 此后, 对排序列表中的每个查询进行二进制搜索; 如果查询的总长度为 L, 则此部分需要 O(Llogn)
时间。
另一种方法, 我们首先反转所有字符串, 以使每个女士的名字都通过在母亲的名字后面加上字母(而不是前缀) 来形成, 并且查询是后缀。
所有查询都放在字典树中, 如 AC 算法。 现在, 每个女士依次可以遍历树,找到与树中的节点匹配的最长后缀。 对于固定大小的字母表, 该算法整体复杂度为O(n)
。
#include<bits/stdc++.h>
#define reg register
#define maxn 1000010
int read()
{
reg int s=0,f=1; reg char ch;
for(;(ch=getchar())<'0'||ch>'9';ch=='-'?f=-f:0);
for(;ch>='0'&&ch<='9';s=s*10+ch-'0',ch=getchar());
return s*f;
}
void write(reg int x) {x<0?x=-x,putchar('-'):0,x>9?write(x/10):void(),putchar(x%10+'0');}
struct edge{int next,to;}e[maxn];
int n,m,num,first[maxn],id[maxn];
char c[maxn],s[maxn];
void add(reg int a,reg int b) {e[++num]=(edge){first[a],b},first[a]=num;}
struct AC
{
int cnt,top,head,tail,t[maxn][30],next[maxn],q[maxn],w[maxn];
void add(reg char s[],reg int k)
{
reg int x=0,len=strlen(s+1);
for(reg int i=len,c;i;--i)
c=s[i]-'A',!t[x][c]?t[x][c]=++cnt:0,x=t[x][c];
id[k]=x;
}
void bfs()
{
for(reg int i=0;i<26;++i)
t[0][i]?q[++tail]=t[0][i]:0;
for(reg int x;head<tail;)
{
x=q[++head];
for(reg int i=0;i<26;++i)
!t[x][i]?t[x][i]=t[next[x]][i]:
(q[++tail]=t[x][i],next[t[x][i]]=t[next[x]][i]);
}
}
void dfs(reg int x,reg int y)
{
++w[y];
for(reg int i=first[x];i;i=e[i].next)
dfs(e[i].to,t[y][c[e[i].to]-'A']);
}
}ac;
int main()
{
n=read(),m=read();
for(reg int i=1,p;i<=n;++i)
c[i]=getchar(),p=read(),p?add(p,i):void();
for(reg int i=1;i<=m;++i) scanf("%s",s+1),ac.add(s,i);
ac.bfs(),ac.dfs(1,ac.t[0][c[1]-'A']);
for(reg int i=ac.cnt;i;--i) ac.w[ac.next[ac.q[i]]]+=ac.w[ac.q[i]];
for(reg int i=1;i<=m;++i) write(ac.w[id[i]]),puts("");
return 0;
}