其他分享
首页 > 其他分享> > [NOI2011] 阿狸的打字机

[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