trie树 & AC自动机 小结
trie树
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符
- 每个节点的所有子节点包含的字符都不相同
我们可以据此来完成
存储
以插入字符串
- 插入
- 根节点不存在子节点
,因此创建子节点 。 - 在节点
的基础上插入第二个字符 。 - 节点
不存在子节点 ,创建子节点 。 - 在节点
的基础上插入第三个字符 。 - 节点
不存在子节点 ,创建子节点 。并将该节点标记为字符串结束标志,完成 字符串插入。
- 插入
- 根节点存在子节点
。不用重新创建子节点 。 - 在节点
的基础上插入第二个字符 。 - 节点
不存在子节点 ,创建子节点 。 - 在节点
的基础上插入第三个字符 。 - 节点
不存在子节点 ,创建子节点 。并将该节点标记为字符串结束标志,完成 字符串插入。
- 插入
- 根节点不存在子节点
,因此创建子节点 。 - 在节点
的基础上插入第二个字符 。 - 节点
不存在子节点 ,创建子节点 。 - 在节点
的基础上插入第三个字符 。 - 节点
不存在子节点 ,创建子节点 。并将该节点标记为字符串结束标志,完成 字符串插入。
- 插入
- 根节点不存在子节点
,因此创建子节点 。 - 在节点
的基础上插入第二个字符 。 - 节点
不存在子节点 ,创建子节点 。并将该节点标记为字符串结束标志,完成 字符串插入。
- 插入
- 根节点存在子节点
,不用重新创建子节点 。 - 在节点
的基础上插入第二个字符 。 - 节点
存在子节点 ,不用重新创建子节点 。 - 在节点
的基础上插入第三个字符 。 - 节点
不存在子节点 ,创建子节点 。 - 在节点
的基础上插入第四个字符 。 - 节点
不存在子节点 ,创建子节点 。并将该节点标记为字符串结束标志,完成 字符串插入。
查询
以找
- 从根节点遍历到
。 - 从节点
遍历到节点 。 - 从节点
遍历到节点 ,查询完成。
例题
【luogu P8306】 【模板】字典树
字典树的模板题,照上面所说做即可,每次增加时给经过的节点的权值
Code
#include<bits/stdc++.h>
using namespace std;
long long a[3000005][66],n,m,cnt,f[3000005];//定义时不用邻接链表,开二维数组存储
long long turn(char x)//将每个字母或数字转化成对应编码
{
if(x<='9')return x-'0'+1;
else if(x<='Z')return x-'A'+12;
return x-'a'+38;
}
void make(string x)//存储字符串
{
long long p=1;//从根节点开始遍历
for(int i=0;i<x.size();i++)
{
if(a[p][turn(x[i])])p=a[p][turn(x[i])];//若存在直接往下查
else a[p][turn(x[i])]=++cnt,p=cnt;//否则新建节点
f[p]++;//权值+1
}
return;
}
long long search(string x)//查询
{
long long p=1;//从根节点开始
for(int i=0;i<x.size();i++)
{
if(a[p][turn(x[i])])p=a[p][turn(x[i])];//往下查
else return 0;//若没有直接返回0
}
return f[p];//返回当前找到节点权值
}
void trie()
{
string x;
cnt=1;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>x;
make(x);
}
for(int i=1;i<=m;i++)
{
cin>>x;
cout<<search(x)<<'\n';
}
for(int i=0;i<=cnt+1;i++)
{
f[i]=0;
for(int j=0;j<=65;j++)a[i][j]=0;
}//清空数组
return;
}
int main()
{
long long qwe;
scanf("%lld",&qwe);
for(int i=1;i<=qwe;i++)trie();
return 0;
}
【luogu P6924】 「EZEC-4」可乐
将每个数转化成二进制,当作字符串存储进
查询时从高位到低位查,若
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,f[2000005],num=1;
long long a[2000005][2];
void dijah(long long x)//存储部分
{
long long p=1;
for(int i=21;i>=0;i--)
{
if(x&(1<<i))
{
if(a[p][1])p=a[p][1];
else a[p][1]=++num,p=num;
}
else
{
if(a[p][0])p=a[p][0];
else a[p][0]=++num,p=num;
}
f[p]++;
}
return;
}
long long gaia(long long p,long long x)//代表现在找到p节点,找到第x位的结果
{
if(x==0)return f[p];
if(m&(1<<x))return max(gaia(a[p][0],x-1)+f[a[p][1]],gaia(a[p][1],x-1)+f[a[p][0]]);//若k该位为1,那么不同任取,相同继续找,取x该位为0或1时答案的最大值即可
return max(gaia(a[p][0],x-1),gaia(a[p][1],x-1));//若该位为0,继续找
}
int main()
{
long long x;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld",&x);
dijah(x);
}
cout<<gaia(1,21);//答案即为从根节点出发,找第21位逐步往后找
return 0;
}
AC自动机
AC自动机是一种多模式匹配算法,将
AC自动机的构造过程分为
- 建立模式串的字典树
- 添加失配路径
- 搜索待处理的文本
建立模式串的字典树
跟前面
添加失配路径
这个步骤是AC自动机中最重要的一步。
添加失配路径即为构建
- 根节点的子节点的
指针都指向根节点,将根节点空的儿子指向自己 - 广度优先搜索,遍历到某个节点时,它的
指针所指即为父亲的 指针所指节点的相同字母儿子 - 若该节点不存在,继续跳
指针即可
举个例子:
先将根节点的子节点的
广度优先搜索到
遍历到第
这样
搜索待处理的文本
从根节点开始往后遍历每个字符,每个字符都跳一遍
例题
【luogu P3796】 AC 自动机(简单版 II)
AC自动机的模板题。
构建
Code
#include<bits/stdc++.h>
using namespace std;
long long num,a[10505][27],co,d[155],s,v[10505],fail[10505];
queue<long long> l;
string b[155];
void dijah(string x)//构建字典树
{
long long p=1;
for(int i=0;i<x.size();i++)
{
if(a[p][x[i]-'a'])p=a[p][x[i]-'a'];
else a[p][x[i]-'a']=++num,p=num;
}
v[p]=++co;//标记
return;
}
void build()
{
for(int i=0;i<26;i++)//遍历root节点的子节点
{
if(a[1][i])
{
fail[a[1][i]]=1;//root节点的子节点的fail指针指向root节点
l.push(a[1][i]);//加入队列准备bfs
}
else a[1][i]=1;//不存在指向root节点
}
long long x;
while(l.size())//bfs
{
x=l.front();
l.pop();
for(int i=0;i<26;i++)//处理出每个儿子的fail指针
{
if(a[x][i])
{
fail[a[x][i]]=a[fail[x]][i];//该节点的fail指针的相同儿子即为该节点相同儿子的fail指针所指
l.push(a[x][i]);//准备bfs
}
else a[x][i]=a[fail[x]][i];//优化,下一次有别的节点跳fail跳到该节点时可以快速转移
}
}
return;
}
void gaia(string x)
{
long long p=1;
for(int i=0;i<x.size();i++)
{
if(a[p][x[i]-'a'])p=a[p][x[i]-'a'];//依次往下找
for(int j=p;j;j=fail[j])//跳fail找答案
{
d[v[j]]++;//统计次数
if(v[j]!=0)s=max(s,d[v[j]]);//取最大
}
}
return;
}
void dijah(long long n)
{
string x;
num=1;
co=s=0;
for(int i=1;i<=n;i++)
{
cin>>b[i];
dijah(b[i]);
}
build();
cin>>x;
gaia(x);
printf("%lld\n",s);
for(int i=1;i<=co;i++)
{
if(d[i]==s)cout<<b[i]<<'\n',d[i]=-1;
}
for(int i=1;i<=num;i++)
{
v[i]=fail[i]=0;
for(int j=0;j<26;j++)a[i][j]=0;
}
for(int i=0;i<=co;i++)d[i]=0;
return;
}
int main()
{
long long n;
while(cin>>n)
{
if(n==0)break;
dijah(n);
}
return 0;
}
【luogu P3041】 [USACO12JAN] Video Game G
AC自动机+DP
处理出
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,a[305][3],num=1,f[305],b[305],v[305],fail[305];
void dijah(string x)
{
long long p=1;
for(int i=0;i<x.size();i++)
{
if(a[p][x[i]-'A'])p=a[p][x[i]-'A'];
else a[p][x[i]-'A']=++num,p=num;
}
v[p]++;
return;
}
queue<long long> l;
void build()
{
long long x;
for(int i=0;i<3;i++)
{
if(a[1][i])
{
fail[a[1][i]]=1;
l.push(a[1][i]);
}
else a[1][i]=1;
}
while(l.size())
{
x=l.front();
l.pop();
for(int i=0;i<3;i++)
{
if(a[x][i])
{
fail[a[x][i]]=a[fail[x]][i];
l.push(a[x][i]);
}
else a[x][i]=a[fail[x]][i];
}
}
return;
}
int main()
{
string x;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
{
cin>>x;
dijah(x);
}
build();
memset(f,-2,sizeof(f));
f[1]=0;
for(int i=1;i<=m;i++)//DP
{
memset(b,-2,sizeof(b));
for(int j=1;j<=num;j++)
{
for(int u=0;u<3;u++)
{
b[a[j][u]]=max(b[a[j][u]],f[j]);//推进到子节点
}
}
for(int j=1;j<=num;j++)
{
f[j]=b[j];//滚动数组
for(int u=j;u!=1;u=fail[u])f[j]+=v[u];//将结尾有模式串的字符串的贡献+1
}
}
long long s=0;
for(int i=1;i<=num;i++)s=max(s,f[i]);
cout<<s;
return 0;
}
【luogu P3121】 [USACO15FEB] Censoring G
开一个栈存储遍历时到过的位置。
可以删除是,将栈顶的元素删除等量多个,退回的将删除的字符串的前面一个节点继续做AC自动机。
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long l,r;
}d[100005];
long long n,num=1,fail[100005],a[100005][26],v[100005],deep[100005],co;
set<long long> v1;
void dijah(string x)
{
long long p=1;
for(int i=0;i<x.size();i++)
{
if(a[p][x[i]-'a'])p=a[p][x[i]-'a'];
else a[p][x[i]-'a']=++num,p=num;
}
v[p]=x.size();
return;
}
queue<long long> l;
void build()
{
for(int i=0;i<26;i++)
{
if(a[1][i])
{
deep[a[1][i]]=1;
l.push(a[1][i]);
fail[a[1][i]]=1;
}
else a[1][i]=1;
}
long long x;
while(l.size())
{
x=l.front();
l.pop();
for(int i=0;i<26;i++)
{
if(a[x][i])
{
deep[a[x][i]]=deep[x]+1;
fail[a[x][i]]=a[fail[x]][i];
l.push(a[x][i]);
}
else a[x][i]=a[fail[x]][i];
}
}
return;
}
stack<long long> re;
void gaia(string x)
{
long long p=1,q,t=-1;
re.push(1);
for(int i=0;i<x.size();i++)
{
p=a[p][x[i]-'a'];
re.push(p);
if(v[p])
{
q=p;
for(int j=1;j<=v[p];j++)re.pop();
p=re.top();//退回到某个节点
d[++co].l=v[q];
d[co].r=i;//存储删去的范围
}
}
return;
}
int main()
{
string x,y;
cin>>x;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
cin>>y;
dijah(y);
}
build();
gaia(x);
for(int i=0;i<x.size();i++)v1.insert(i);
set<long long>::iterator q,w;
for(int i=1;i<=co;i++)
{
q=v1.lower_bound(d[i].r);
for(int j=1;j<=d[i].l;j++)
{
w=q;
w--;
v1.erase(q);
q=w;
}
}//用set找未被删的位
q=v1.begin();
for(;q!=v1.end();q++)
{
cout<<x[*q];
}
return 0;
}
【luogu P2444】 [POI2000] 病毒
即使找存不存在一个环不经过任何一个作为字符串结尾的节点,暴力
Code
#include<bits/stdc++.h>
using namespace std;
long long n,a[30005][2],num=1,v[30005],fail[30005],b[30005],f[30005];
bool qwe=false;
void dijah(string x)
{
long long p=1;
for(int i=0;i<x.size();i++)
{
if(a[p][x[i]-'0'])p=a[p][x[i]-'0'];
else a[p][x[i]-'0']=++num,p=num;
}
v[p]++;
return;
}
queue<long long> l;
void build()
{
for(int i=0;i<2;i++)
{
if(a[1][i])
{
fail[a[1][i]]=1;
l.push(a[1][i]);
}
else a[1][i]=1;
}
long long x;
while(l.size())
{
x=l.front();
l.pop();
for(int i=0;i<2;i++)
{
if(a[x][i])
{
fail[a[x][i]]=a[fail[x]][i];
l.push(a[x][i]);
}
else a[x][i]=a[fail[x]][i];
}
}
return;
}
void dijah(long long x)
{
for(int j=x;j!=1;j=fail[j])
{
if(v[j])return;//若作为结尾直接返回
}
if(b[x])//若以遍历过则成功找到环,输出TAK
{
qwe=true;
return;
}
b[x]=1;
if(!f[a[x][0]])dijah(a[x][0]);//往下找
if(!f[a[x][1]])dijah(a[x][1]);
b[x]=0;
f[x]=1;
return;
}
int main()
{
string x;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
cin>>x;
dijah(x);
}
build();
dijah(1);
if(!qwe)printf("NIE\n");
else printf("TAK\n");
return 0;
}
【luogu P2414】 [NOI2011] 阿狸的打字机
AC自动机fail指针所指能够成一个树为
用树状数组单点修改与
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int x,y,z,v;
}que[100005];
int a[100005][26],m,fa[100005],num,fail[100005],f[100005],n,dfn[100005],out[100005],q=1,g,num1,last[100005];
vector<int> t[100005];
string p;
bool cmp1(datay q,datay w)
{
return q.y<w.y;
}
queue<int> l;
bool cmp2(datay q,datay w)
{
return q.z<w.z;
}
int lowbit(int x)
{
return x&(-x);
}
void dijah(int x,int y)
{
// cout<<x<<'\n';
for(int i=x;i<=num;i+=lowbit(i))f[i]+=y;
return;
}
int gaia(int x)
{
int h=0;
while(x)
{
h+=f[x];
x-=lowbit(x);
}
return h;
}
void build()
{
for(int i=0;i<26;i++)
{
if(a[1][i])
{
l.push(a[1][i]);
fail[a[1][i]]=1;
}
else a[1][i]=1;
}
int x;
while(l.size())
{
x=l.front();
l.pop();
for(int i=0;i<26;i++)
{
if(a[x][i])
{
l.push(a[x][i]);
fail[a[x][i]]=a[fail[x]][i];
}
else a[x][i]=a[fail[x]][i];
}
}
return;
}
void dfs(int x)
{
dfn[x]=++num1;
for(int i=0;i<t[x].size();i++)
{
dfs(t[x][i]);
}
out[x]=num1;
return;
}
void modify()
{
for(;g<p.size();g++)
{
// cout<<g<<' '<<q<<'\n';
if(p[g]=='B')dijah(dfn[q],-1),q=fa[q];
else if(p[g]=='P')
{
g++;
return;
}
else
{
q=a[q][p[g]-'a'];
dijah(dfn[q],1);
}
}
return;
}
int main()
{
cin>>p;
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&que[i].x,&que[i].y);
que[i].z=i;
}
sort(que+1,que+m+1,cmp1);
num=1;
q=1;
for(int i=0;i<p.size();i++)
{
if(p[i]=='B')q=fa[q];
else if(p[i]=='P')last[++n]=q;
else
{
if(a[q][p[i]-'a'])q=a[q][p[i]-'a'];
else a[q][p[i]-'a']=++num,fa[num]=q,q=num;
}
}
build();
for(int i=2;i<=num;i++)
{
t[fail[i]].push_back(i);
}
// cout<<"doge\n";
q=1;
dfs(1);
// for(int i=1;i<=num;i++)cout<<fa[i]<<' '<<fail[i]<<' '<<dfn[i]<<' '<<out[i]<<'\n';
g=0;
for(int i=1;i<=m;i++)
{
if(que[i].y!=que[i-1].y)
{
for(int j=que[i-1].y+1;j<=que[i].y;j++)modify();
}
que[i].v=gaia(out[last[que[i].x]])-gaia(dfn[last[que[i].x]]-1);
}
sort(que+1,que+m+1,cmp2);
for(int i=1;i<=m;i++)printf("%d\n",que[i].v);
return 0;
}
【CF1202E】 You Are Given Some Strings...
先正着坐一遍AC自动机,记录到每个点的字符串个数,再把所有字符串反过来再做一遍,两边相乘后累加即可。
Code
#include<bits/stdc++.h>
using namespace std;
string p,t[400005];
long long f1[400005],n,a[400005][26],num=1,v[400005],fail[400005],f2[400005],s=0;
queue<long long> l;
void dijah(string x)
{
long long q=1;
for(int i=0;i<x.size();i++)
{
if(a[q][x[i]-'a'])q=a[q][x[i]-'a'];
else a[q][x[i]-'a']=++num,q=num;
}
v[q]++;
return;
}
void build()
{
long long x;
for(int i=0;i<26;i++)
{
if(a[1][i])
{
l.push(a[1][i]);
fail[a[1][i]]=1;
v[a[1][i]]+=v[1];
}
else a[1][i]=1;
}
while(l.size())
{
x=l.front();
l.pop();
for(int i=0;i<26;i++)
{
if(a[x][i])
{
l.push(a[x][i]);
fail[a[x][i]]=a[fail[x]][i];
v[a[x][i]]+=v[fail[a[x][i]]];
}
else a[x][i]=a[fail[x]][i];
}
}
return;
}
void gaia1()
{
long long q=1;
for(int i=0;i<p.size();i++)
{
q=a[q][p[i]-'a'];
f1[i]=v[q];
}
return;
}
void gaia2()
{
long long q=1;
for(int i=0;i<p.size();i++)
{
q=a[q][p[i]-'a'];
f2[i]=v[q];
}
return;
}
string h="";
string re(string x)
{
h="";
for(int i=x.size()-1;i>=0;i--)h+=x[i];
return h;
}
int main()
{
cin>>p;
scanf("%lld",&n);
for(int i=1;i<=n;i++)cin>>t[i],dijah(t[i]);
build();
gaia1();
memset(a,0,sizeof(a));
memset(v,0,sizeof(v));
memset(fail,0,sizeof(fail));
while(l.size())l.pop();
num=1;
p=re(p);
for(int i=1;i<=n;i++)t[i]=re(t[i]),dijah(t[i]);
build();
gaia2();
for(int i=0;i<p.size()-1;i++)s+=f1[i]*f2[p.size()-i-2];
cout<<s;
return 0;
}
【luogu P2292】 [HNOI2004] L 语言
由于
那么在AC自动机匹配时,可以记录当前字符的前
由于
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,num=1,fail[1005],a[1005][36],v[1005],f[1005];
//vector<int> v[1005];
void dijah(string x)
{
int p=1;
for(int i=0;i<x.size();i++)
{
if(a[p][x[i]-'a'])p=a[p][x[i]-'a'];
else a[p][x[i]-'a']=++num,p=num;
}
v[p]=x.size();
return;
}
queue<int> l;
void build()
{
int x;
for(int i=0;i<26;i++)
{
if(a[1][i])
{
l.push(a[1][i]);
fail[a[1][i]]=1;
}
else a[1][i]=1;
}
while(l.size())
{
x=l.front();
l.pop();
for(int i=0;i<26;i++)
{
if(a[x][i])
{
l.push(a[x][i]);
fail[a[x][i]]=a[fail[x]][i];
}
else a[x][i]=a[fail[x]][i];
}
}
return;
}
int main()
{
string x;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
cin>>x;
dijah(x);
}
build();
for(int i=2;i<=num;i++)
{
for(int j=i;j!=1;j=fail[j])
{
if(v[j])f[i]+=(1<<(v[j]));
}
// cout<<i<<':'<<fail[i]<<' '<<f[i]<<'\n';
}
int q=(1<<21);
// for(int )
for(int i=1;i<=m;i++)
{
x="";
char c=getchar();
while(c<'a'||c>'z')c=getchar();
while(c>='a'&&c<='z')x+=c,c=getchar();
// x=read();
int p=1,s=2,ans=-1;
for(int j=0;j<x.size();j++)
{
p=a[p][x[j]-'a'];
if(s&f[p])s++,ans=j;
// cout<<j<<' '<<s<<' '<<p<<' '<<f[p]<<'\n';
s<<=1;
if(s&q)s^=q;
}
printf("%d\n",ans+1);
}
return 0;
}
【CF86C】 Genetic engineering
AC自动机+DP
设
若当前节点有作为字符串结尾且长度大于
否则向下一位推进。
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+9;
int n,m,a[105][4],num=1,fail[105];
long long v[105];
long long f[1005][105][12];
queue<int> l;
//vector<int> t[105];
void build()
{
int x;
for(int i=0;i<4;i++)
{
if(a[1][i])
{
l.push(a[1][i]);
fail[a[1][i]]=1;
}
else a[1][i]=1;
}
while(l.size())
{
x=l.front();
l.pop();
for(int i=0;i<4;i++)
{
if(a[x][i])
{
l.push(a[x][i]);
fail[a[x][i]]=a[fail[x]][i];
v[a[x][i]]=max(v[a[x][i]],v[fail[a[x][i]]]);
}
else a[x][i]=a[fail[x]][i];
}
}
return;
}
int main()
{
// memset(v,-1,sizeof(v));
int p=1;
long long s=0;
char c;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
s=0;
p=1;
c=getchar();
while(c<'A'||c>'Z')c=getchar();
while(c>='A'&&c<='Z')
{
s++;
if(c=='A')c='a';
else if(c=='C')c='b';
else if(c=='G')c='c';
else c='d';
if(a[p][c-'a'])p=a[p][c-'a'];
else a[p][c-'a']=++num,p=num;
c=getchar();
}
v[p]=max(v[p],s);
}
build();
// for(int i=1;i<=num;i++)cout<<i<<' '<<fail[i]<<' '<<v[i]<<'\n';
// memset(f,1,sizeof(f));
f[0][1][0]=1;
// for(int i=1;i<=num;i++)
// {
// for(int j=0;j<4;j++)
// {
// if(!b[i][a[i][j]])
// {
// b[i][a[i][j]]=1;
// t[i].push_back(a[i][j]);
// }
// }
// }
for(int i=0;i<=n;i++)
{
for(int j=1;j<=num;j++)
{
for(int u=0;u<=10;u++)
{
for(int k=0;k<4;k++)
{
// if(t[j][k]==1)continue;
if(v[a[j][k]]>u)f[i+1][a[j][k]][0]=(f[i+1][a[j][k]][0]+f[i][j][u])%mod;
else f[i+1][a[j][k]][u+1]=(f[i+1][a[j][k]][u+1]+f[i][j][u])%mod;
// if(i==0&&j==1&&u==0&&a[j][k]==2)cout<<f[i+1][a[j][k]][0]<<'\n';
}
// for(int x1=j;x1!=1;x1=fail[x1])
// {
// if(v[x1])
// }
}
}
}
s=0;
for(int i=1;i<=num;i++)s=(s+f[n][i][0])%mod;
cout<<s;
return 0;
}
【luogu P5840】 [COCI2015] Divljak
很明显,建好AC自动机,构建
问题是怎么去重。
我们可以每次增加字符串时,把它经过的点给求出来,求路径并即可。
Code
#include<bits/stdc++.h>
using namespace std;
int n,num=1,a[2000005][26],v[2000005],m,fail[2000005],f[2000005][22],deep[2000005],dfn[2000005],out[2000005],num1,f1[2000005],d[2000005],tr;
queue<int> l;
vector<int> t[2000005];
int lowbit(int x)
{
return x&(-x);
}
void dijah(int x,int y)
{
// cout<<x<<' '<<y<<'\n';
if(x==0)return;
for(int i=x;i<=num;i+=lowbit(i))f1[i]+=y;
return;
}
int gaia(int x)
{
int h=0;
while(x)
{
h+=f1[x];
x-=lowbit(x);
}
return h;
}
void build()
{
for(int i=0;i<26;i++)
{
if(a[1][i])
{
l.push(a[1][i]);
fail[a[1][i]]=1;
}
else a[1][i]=1;
}
int x;
while(l.size())
{
x=l.front();
l.pop();
for(int i=0;i<26;i++)
{
if(a[x][i])
{
l.push(a[x][i]);
fail[a[x][i]]=a[fail[x]][i];
}
else a[x][i]=a[fail[x]][i];
}
}
return;
}
void dfs(int x,int y)
{
deep[x]=deep[y]+1;
f[x][0]=y;
dfn[x]=++num1;
for(int i=0;i<t[x].size();i++)
{
dfs(t[x][i],x);
}
out[x]=num1;
return;
}
int LCA(int x,int y)
{
if(deep[x]<deep[y])swap(x,y);
for(int i=21;i>=0;i--)
{
if(deep[f[x][i]]>=deep[y])x=f[x][i];
}
if(x==y)return x;
for(int i=21;i>=0;i--)
{
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
}
return f[x][0];
}
bool cmp(int q,int w)
{
return dfn[q]<dfn[w];
}
int main()
{
int p=1,x,y;
scanf("%d",&n);
char c=getchar();
for(int i=1;i<=n;i++)
{
p=1;
while(c<'a'||c>'z')c=getchar();
while(c>='a'&&c<='z')
{
if(a[p][c-'a'])p=a[p][c-'a'];
else a[p][c-'a']=++num,p=num;
c=getchar();
}
v[i]=p;
}
build();
for(int i=2;i<=num;i++)t[fail[i]].push_back(i);
dfs(1,0);
for(int i=1;i<=21;i++)
{
for(int j=1;j<=num;j++)f[j][i]=f[f[j][i-1]][i-1];
}
// for(int i=1;i<=num;i++)
// {
// printf("%d:%d %d %d\n",i,fail[i],dfn[i],out[i]);
// }
scanf("%d",&m);
for(int qw=1;qw<=m;qw++)
{
scanf("%d",&p);
if(p==1)
{
tr=0;
p=1;
while(c<'a'||c>'z')c=getchar();
while(c>='a'&&c<='z')
{
// cout<<"doge\n";
p=a[p][c-'a'];
d[++tr]=p;
c=getchar();
}
sort(d+1,d+tr+1,cmp);
// for(int i=1;i<=tr;i++)cout<<d[i]<<' ';
// cout<<'\n';
for(int i=1;i<=tr;i++)
{
dijah(dfn[d[i]],1);
if(i>1)dijah(dfn[LCA(d[i-1],d[i])],-1);
// if(df)
// if(i>1&&out[dfn[LCA(d[i-1],d[i])]]<dfn[d[i]])cout<<LCA(d[i-1],d[i])<<' '<<d[i-1]<<' '<<d[i]<<'\n';
// if(i>1&&dfn[LCA(d[i-1],d[i])]>dfn[d[i]])cout<<"doge\n";
}
}
else
{
scanf("%d",&x);
printf("%d\n",gaia(out[v[x]])-gaia(dfn[v[x]]-1));
}
}
return 0;
}
/*
5
abc
ab
cab
bc
abd
3132
1 aacbabcbbc
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧