洛谷P2414/LOJ2444/BZOJ2434[NOI2011]阿狸的打字机(AC自动机+树状数组)
由于AC自动机是1975年被发明的,所以这个打字机也可以算是上古科技
首先考察AC自动机的性质我们可以知道,fail指针被发明出来就是为了沿着它跳能跳到所有当前结点代表的字符串的后缀在Trie上对应的结点,所以我们只要统计出y里面有多少个结点跳fail可以跳到x的结尾就可以了。
然而这个东西肯定不能暴力跳。。。不难看出把fail变成无向边的话,fail转移形成一棵树(fail树),如果我们把y的结点都在树上染色的话,x的答案就是它的子树中有几个结点被染色,这个可以dfs序+树状数组。
那么问题在于如何快速给y的所有结点染色。考虑离线,在Trie上跑dfs,每个点进入时染色,退出时恢复,那么跑到y的结尾时刚好y的所有结点被染色。事先把所有关于y的询问存在这里,统一处理即可。
#include<cstdio> #include<cctype> #include<cstring> #include<vector> #include<queue> using namespace std; const int N=100050; int f[N],ch[N][26],ch2[N][26],cnt=0,pos[N],fail[N],ans[N],dfn[N],dfl[N],dfsc=0,c[N],G[N],to[N],nxt[N],sz=0; char s[N]; vector<int> qry[N],qp[N]; queue<int> Q; inline void bfs(){ int i,u; for(i=0;i<26;++i)if(ch[0][i])Q.push(ch[0][i]); while(!Q.empty()){ u=Q.front();Q.pop(); for(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]; } } inline void adde(int u,int v){ to[++sz]=v;nxt[sz]=G[u];G[u]=sz; } inline void add(int x,short k){ for(;x<=cnt+1;x+=x&-x)c[x]+=k; } inline int sum(int x){ int s=0; for(;x;x^=x&-x)s+=c[x]; return s; } void dfs1(int u){ dfn[u]=++dfsc; for(int i=G[u];i;i=nxt[i])dfs1(to[i]); dfl[u]=dfsc; } void dfs2(int u){ int i; add(dfn[u],1); for(i=0;i<qry[u].size();++i)ans[qp[u][i]]=sum(dfl[qry[u][i]])-sum(dfn[qry[u][i]]-1); for(i=0;i<26;++i)if(ch2[u][i])dfs2(ch2[u][i]); add(dfn[u],-1); } int main(){ int q,i,x,y,l,n=0,k=0; scanf("%s",s);l=strlen(s); for(i=0;i<l;++i)if(islower(s[i])){ if(!ch[k][s[i]-'a'])ch[k][s[i]-'a']=++cnt; f[ch[k][s[i]-'a']]=k;k=ch[k][s[i]-'a']; }else if(s[i]=='B')k=f[k]; else pos[++n]=k; memcpy(ch2,ch,sizeof(ch2)); bfs(); for(i=1;i<=cnt;++i)adde(fail[i],i); dfs1(0); scanf("%d",&q); for(i=0;i<q;++i){ scanf("%d%d",&x,&y); qry[pos[y]].push_back(pos[x]);qp[pos[y]].push_back(i); } dfs2(0); for(i=0;i<q;++i)printf("%d\n",ans[i]); return 0; }