洛谷P2414 [NOI2011]阿狸的打字机

传送门

询问y中x出现了多少次,相当于询问y中的节点通过fail指针能到达x的ed节点的有多少个。

那么一种比较暴力的做法就是,建出AC自动机,把询问按y排个序,遍历trie树,每次暴力跳fail,开个cnt数组维护对每个x的答案。

考虑y上的点跳fail指针最终到达某个ed点的过程,把fail指针当作边并且反向,发现y上能跳到x的ed的节点都在fail树上x的子树中。如果把fail树建出来,现在只需要考虑怎么统计x的ed点的子树中有多少个属于y的点。

结合刚刚暴力的做法,发现遍历trie树走到y的ed节点时,栈内保存的经过点就是y串。也就是说,在走到y的ed点时,可以做到将y上的所有点都标记出来,并且只标记y上的点。这个时候对于一个询问(x,y),只需要求fail树上x的ed点的子树中有多少个被标记出来的点。dfs一遍fail树,将每个点映射到dfs序。子树的dfs序是连续的,于是问题就变成了询问一段连续区间内的值,树状数组维护即可。

对于trie树上的每个点开个vector记录它是哪个串的ed点,遍历到一个点的时候处理以它结尾的串的询问。这里的做法没有排序询问,而把每个询问存入y的vector中,处理每个串的时候遍历这个vector。

时间复杂度是O(nlogn)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int N=1e5+10;
char s[N];
int fa[N],lens,now,tree[N][26],tree1[N][26],num,ed[N],tot,m,fail[N],ans[N];
struct node{
    int x,id;
    node(int a=0,int b=0){
        x=a,id=b;
    }
};
vector<node>v[N];
vector<int>e[N];
void insert(){
    for(int j=1;j<=lens;j++){
        if(s[j]=='B')now=fa[now];
        else if(s[j]=='P'){
            num++;
            ed[num]=now;
            e[now].push_back(num);
        }
        else{
            if(!tree[now][s[j]-'a'])tree1[now][s[j]-'a']=tree[now][s[j]-'a']=++tot,fa[tot]=now;
            now=tree[now][s[j]-'a'];
        }
    }
}
int Next[N],head[N],tot1,ver[N];
void add(int x,int y){
    ver[++tot1]=y;
    Next[tot1]=head[x];
    head[x]=tot1;
}
queue<int>q0;
void getfail(){
    for(int i=0;i<26;i++){
        if(tree[0][i]){
            add(0,tree[0][i]);
            q0.push(tree[0][i]);
        }
    }
    while(q0.size()){
        int u=q0.front();
        q0.pop();
        for(int i=0;i<26;i++){
            int v=tree1[u][i];
            if(v){
                fail[v]=tree1[fail[u]][i];
                add(fail[v],v);
                q0.push(v);
            }
            else tree1[u][i]=tree1[fail[u]][i];
        }
    }
}
int tim,rec[N],rec1[N];
void dfs(int x){
    rec[x]=++tim;
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs(y);
    }
    rec1[x]=tim;
}
int tr[N];
void add1(int x,int val){
    for(;x<=tim;x+=(x&-x))tr[x]+=val;
}
int ask(int x){
    int sum=0;
    for(;x;x-=(x&-x))sum+=tr[x];
    return sum;
}
void solve(int x){
    for(int i=0;i<v[x].size();i++){
        node y=v[x][i];
        ans[y.id]=ask(rec1[ed[y.x]])-ask(rec[ed[y.x]]-1);
    }
}
void dfs1(int x){
    if(e[x].size()){
        for(int i=0;i<e[x].size();i++){
            int y=e[x][i];
            solve(y);
        }
    }
    for(int i=0;i<26;i++){
        if(tree[x][i]){
            add1(rec[tree[x][i]],1);
            dfs1(tree[x][i]);
            add1(rec[tree[x][i]],-1);
        }
    }
}
int main()
{
    scanf("%s",s+1);
    lens=strlen(s+1);
    insert();
    getfail();
    dfs(0);
    scanf("%d",&m);
    for(int i=1,x,y;i<=m;i++){
        scanf("%d%d",&x,&y);
        v[y].push_back(node(x,i));
    }
    dfs1(0);
    for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
    return 0;
 } 
P2414 [NOI2011]阿狸的打字机

·建立trie树的时候直接根据操作在树上走动就好了。操作是小写字母的话就往儿子走一步,操作B则往父亲走一步,操作P的话记录当前点为当前串的ed节点并把当前串丢进节点的vector里去。用不着每次暴力插入一个新串,正解被我硬生生搞成70分暴力…

·注意映射关系的细节。

·由于最后还要遍历一遍trie树,前面getfail的时候不能把原trie树建成trie图,可以备份一棵trie树用来getfail。

posted @ 2019-12-13 07:12  Chloris_Black  阅读(153)  评论(1编辑  收藏  举报