【bzoj2434】[Noi2011]阿狸的打字机【AC自动机】
题目传送门
题解:由于fail[i]一定是i的一个后缀,所以i这个节点对应的状态一定会在fail树中i的子树的所有状态出现。我们只需要对每个询问x,y求出x对应节点子树内有多少个节点在y对应节点到根的路径上出现过。我们先建出trie树,再把AC自动机搞出来(不要建Trie图!),然后把fail链倒过来建出fail树。接着我们把fail树dfs一遍,得到每个节点的入栈时间l[i]和出栈时间r[i],i对应子树的dfs序区间就是[l[i],r[i]]。我们再dfs一遍原来的trie树,每遇到一个点i就c[l[i]]++,接着对于以i为y串的所有询问的x串对应节点求一下c[l[x]]~c[r[x]]的和,记录一下答案,继续dfs,最后退栈的时候c[l[i]]–。这很显然可以用树状数组解决。于是就搞掂了!
我感觉这次我打的AC自动机好丑啊,而且常数巨大(sb自带大常数)。
代码
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int N=100005;
int n,m,idx,now,cnt=1,dfc,tot,x,y,dy[N],fa[N],fail[N],ch[N][26];
int head[N],to[N],nxt[N],l[N],r[N],c[N],ans[N];
char s[N];
queue<int> q;
vector<int> v[N],v2[N];
void adde(int u,int v){
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
void build_ac(){
q.push(1);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<26;i++){
if(ch[u][i]){
int p;
if(u==1){
p=1;
}else{
p=fail[u];
while(p!=1&&!ch[p][i]){
p=fail[p];
}
if(!ch[p][i]){
p=1;
}else{
p=ch[p][i];
}
}
fail[ch[u][i]]=p;
adde(p,ch[u][i]);
q.push(ch[u][i]);
}
}
}
}
void dfs1(int u){
l[u]=++dfc;
for(int i=head[u];i;i=nxt[i]){
dfs1(to[i]);
}
r[u]=dfc;
}
void add(int i,int v){
while(i<=dfc){
c[i]+=v;
i+=i&(-i);
}
}
int sum(int i){
int res=0;
while(i){
res+=c[i];
i-=i&(-i);
}
return res;
}
void dfs2(int u){
add(l[u],1);
for(int i=0;i<v[u].size();i++){
ans[v2[u][i]]=sum(r[v[u][i]])-sum(l[v[u][i]]-1);
}
for(int i=0;i<26;i++){
if(ch[u][i]){
dfs2(ch[u][i]);
}
}
add(l[u],-1);
}
int main(){
scanf("%s%d",s+1,&m);
n=strlen(s+1);
now=1;
for(int i=1;i<=n;i++){
if(s[i]>='a'&&s[i]<='z'){
if(!ch[now][s[i]-'a']){
ch[now][s[i]-'a']=++cnt;
fa[cnt]=now;
}
now=ch[now][s[i]-'a'];
}else if(s[i]=='P'){
dy[++idx]=now;
}else{
now=fa[now];
}
}
build_ac();
dfs1(1);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
v[dy[y]].push_back(dy[x]);
v2[dy[y]].push_back(i);
}
dfs2(1);
for(int i=1;i<=m;i++){
printf("%d\n",ans[i]);
}
return 0;
}