[NOI2011] 阿狸的打字机
作者:互联网
题意
给定n个字符串,m次询问(\(n,m \leq 1e5\))每次询问一个串在另一串的出现次数,可以离线处理
思路
首先肯定是对n个串建AC自动机
如何求一个串的子串?假设i点对应这个字符串,那么从fail[ i ]就表示i串的最长后缀,一直向上跳fail,就可以得到i串的所有后缀。对于i的所有子串,我们可以理解为i的所有后缀的所有前缀,即每个后缀的前缀对应一个子串,他们一一对应。
由于i在AC自动机上的父亲就是它的前缀,而一个点跳fail又可以跳出所有后缀,所以求i的子串等价于求i点到根节点的路径上所有点分别跳fail得到的结果
然而,一步步跳fail显然太暴力,对于aaaaaaa...这种数据很显然过不了,这个时候可以考虑fail树。由于j串对应的点只有一个,而如果i的一个前缀对应的节点p在j的子树中,那么p一定可以通过跳fail跳到j,此时ans应该加一
子树求和?
这时就可以想到离线做法了,dfs一遍原trie树,进入一个点将它在fail树上的权值加一,退出时减去,如果dfs到i点,那么就统计子树目标j在fail树上的子树和即为答案,子树求和怎么做?遍历fail树求一遍dfn序即可用树状数组维护
Code:
#include<bits/stdc++.h>
#define N 100005
#define lowbit(x) ((x)&(-x))
using namespace std;
int n,m,nxt[N][26],nnxt[N][26],fail[N],end[N],fa[N],hfu,ndsum;
int dfn[N],size[N],col;
int ans[N],c[N];
char a[N],b[N];int top;
struct Node {int son,i;};
struct Edge
{
int next,to;
}edge[N<<1];int head[N],cnt;
vector <Node> nd[N];
void add_edge(int from,int to)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
head[from]=cnt;
}
void read(int &x)
{
char c;int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void update(int x,int val) {for(;x<=col;x+=lowbit(x)) c[x]+=val;}
int query(int x) {int ret=0;for(;x;x-=lowbit(x)) ret+=c[x];return ret;}
int ask(int l,int r) {return query(r)-query(l-1);}
void get_fail()
{
queue<int> q;
for(int i=0;i<26;++i) if(nxt[0][i]) q.push(nxt[0][i]);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=0;i<26;++i)
{
if(nxt[u][i])
{
q.push(nxt[u][i]);
fail[nxt[u][i]]=nxt[fail[u]][i];
}
else nxt[u][i]=nxt[fail[u]][i];
}
}
}
void work()
{
int len=strlen(a),now=0;
for(int i=0;i<len;++i)
{
if(a[i]!='B'&&a[i]!='P')
{
if(!nxt[now][a[i]-'a']) nxt[now][a[i]-'a']=++ndsum,fa[ndsum]=now;
now=nxt[now][a[i]-'a'];
}
if(a[i]=='B') now=fa[now];
if(a[i]=='P') end[++hfu]=now;
}
}
void dfs(int rt)//fail dfn
{
size[rt]=1;
dfn[rt]=++col;
for(int i=head[rt];i;i=edge[i].next) {dfs(edge[i].to);size[rt]+=size[edge[i].to];}
}
void dfs1(int rt)//solve
{
update(dfn[rt],1);
if(!nd[rt].empty())
{
for(int i=0;i<(int)nd[rt].size();++i)
{
int pt=dfn[nd[rt][i].son];
ans[nd[rt][i].i]=ask(pt,pt+size[nd[rt][i].son]-1);
}
}
for(int i=0;i<26;++i) if(nnxt[rt][i]) dfs1(nnxt[rt][i]);
update(dfn[rt],-1);
}
int main()
{
scanf("%s",a);
work();
for(int i=0;i<=ndsum;++i)
for(int j=0;j<26;++j) nnxt[i][j]=nxt[i][j];
get_fail();
read(m);
for(int i=1;i<=m;++i)
{
int x,y;
read(x);read(y);
nd[end[y]].push_back((Node){end[x],i});
}
for(int i=1;i<=ndsum;++i) add_edge(fail[i],i);
dfs(0);
dfs1(0);
for(int i=1;i<=m;++i) printf("%d\n",ans[i]);
return 0;
}
标签:子树,前缀,阿狸,int,edge,后缀,打字机,NOI2011,fail 来源: https://www.cnblogs.com/Chtholly/p/11228653.html