[NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]

题面

传送门

正文

最暴力的

最暴力的方法:把所有询问代表的字符串跑一遍kmp然后输出

稍微优化一下:把所有询问保存起来,把模板串相同的合并,求出next然后匹配

但是这两种方法本质没有区别,都是暴力

不那么暴力的

我们对于所有的串建立一个AC自动机,把询问按照$y$排序,然后在AC自动机上面跑,每次跳fail更新答案

这样可以拿到70分,但是时间上限还是会$O\left(n^2\right)$左右

巧妙的优化

这道题里面,所有的模板串和文本串都在AC自动机里

那么,题目中实际是在要求什么呢?

就是有多少个x串是y串的一个前缀的后缀

那么,在AC自动机自己身上有没有满足这样的检索的结构呢?

有的,那就是fail指针

trie上的某一个前缀的fail指针,指向的是作为它的最长后缀的那个节点;同时,从某个前缀开始一路沿着fail指针跳,直到根节点,过程中所有的节点代表的前缀都是这个前缀的后缀

也就是说,我们把fail指针看成树边,将这个“fail树”(不要和kmp的next树搞混了)提取出来,那么我们就可以把题目的询问变成这样:

把代表y串的所有前缀的节点打上标记,那么代表x串的节点的子树中的标记个数,就是这个询问的答案

维护个数和可以用fail树上的dfs序以及树状数组共同完成

正解

上述过程中有一个重复的地方:每次我们都需要把树状数组归零,然后重新把新的y串前缀节点插进去——即使我们使用把y排序的方法也会TLE

但是这个过程中有一个问题:有些点会进进出出好多遍,并不高效,我们需要找到一个办法,使得每个AC自动机上的点只进出树状数组一次

那么谁能满足这个要求呢?

还是dfs序,只不过是原trie树上的dfs序

我们把输入的询问按照y串在trie树上的dfs序排序,依次加入、删除

因为按照dfs序遍历可以使每个点进入一次离开一次,所以这个方法的总时间效率只有$O\left(nlogn\right)$

这样这道题就做完了

Code

本题的映射非常多,而且很繁复,有很多重复意义的东西,调试的时候一定要小心

变量名有点乱,还请见谅

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define rank deep_dark_fantasy
using namespace std;
struct node{
    int fail,fa,son[26];
    vector<int>num;
    node(){fail=fa=0;memset(son,0,sizeof(son));num.clear();}
}a[100010];int cnt,tot;
int dfn[100010],clk,end[100010],tmplca,pre[100010],rank[100010];
//dfn是trie树dfs序,rank是dfn的反映射
//end是每个字符串在trie树上的节点编号
//pre表示由dfs序为i的串向dfs序为i+1的串转移时的lca,tmplca是维护这个的辅助变量
struct edge{
    int to,next;
}e[100010];int cnte,first[100010];
inline void addedge(int u,int v){
    e[++cnte]=(edge){v,first[u]};first[u]=cnte;
}
inline void add(char s[]){
    int len=strlen(s),cur=0,i;
    for(i=0;i<len;i++){
        if(s[i]=='P'){a[cur].num.push_back(++tot);continue;}
        if(s[i]=='B'){cur=a[cur].fa;continue;}
        if(!a[cur].son[s[i]-'a']) a[cur].son[s[i]-'a']=++cnt;
        a[a[cur].son[s[i]-'a']].fa=cur;cur=a[cur].son[s[i]-'a'];
    }
}
void getdfn(int u){
    int i,v,len=a[u].num.size();
    for(i=0;i<len;i++){
        dfn[++clk]=a[u].num[i];
        rank[a[u].num[i]]=clk;
        end[a[u].num[i]]=u;
        pre[clk]=tmplca;tmplca=u;
    }
    for(i=0;i<26;i++){
        v=a[u].son[i];if(!v) continue;
        getdfn(v);tmplca=u;
    }
}
int q[100010];
void getfail(){
    int head=0,tail=0,i,u,v;
    for(i=0;i<26;i++){
        if(!a[0].son[i]) continue;
        a[a[0].son[i]].fail=0;q[tail++]=a[0].son[i];
    }
    while(head<tail){
        u=q[head++];
        for(i=0;i<26;i++){
            v=a[u].son[i];
            if(v) a[v].fail=a[a[u].fail].son[i],q[tail++]=v;
            else a[u].son[i]=a[a[u].fail].son[i];
        }
    }
    memset(first,-1,sizeof(first));
    for(i=1;i<=cnt;i++) addedge(a[i].fail,i);
}
char s[100010];int Q;
struct query{
    int x,y,num,ans;
}qq[100010];
bool cmp(query l,query r){return rank[l.y]<rank[r.y];}
bool cmp2(query l,query r){return l.num<r.num;}
int now=0,tmpnow;
struct tree{//树状数组
    int x[100010];
    tree(){memset(x,0,sizeof(x));}
    int lowbit(int pos){return pos&(-pos);}
    void change(int pos,int type){
        for(int i=pos;i<=cnt+1;i+=lowbit(i)) x[i]+=type;
    }
    int ask(int pos){
        int re=0;
        for(int i=pos;i>0;i-=lowbit(i)) re+=x[i];
        return re;
    }
}T;
int faildfn[100010],failclk=0,le[100010],ri[100010];
//faildfn是fail树上的dfs序,le和ri是某个节点在树状数组上的左右区间
void get_fail_dfn(int u){
    int i,v;faildfn[u]=++failclk;le[u]=failclk;
    for(i=first[u];~i;i=e[i].next){
        v=e[i].to;
        get_fail_dfn(v);
    }
    ri[u]=failclk;
}
int main(){
    scanf("%s",s);int i,j,x,y,xx;
    add(s);getdfn(0);
    getfail();get_fail_dfn(0);

    scanf("%d",&Q);
    for(i=1;i<=Q;i++) scanf("%d%d",&qq[i].x,&qq[i].y),qq[i].num=i;
    sort(qq+1,qq+Q+1,cmp);//排序

    j=1;
    for(i=1;i<=tot;i++){
        y=dfn[i];tmpnow=end[y];
        while(now!=pre[i]){
            T.change(faildfn[now],-1);now=a[now].fa;
        }
        while(tmpnow!=now){
            T.change(faildfn[tmpnow],1);tmpnow=a[tmpnow].fa;
        }//插入、删除节点
        now=end[y];
        while(qq[j].y==y){//处理询问
            xx=end[qq[j].x];
            qq[j].ans=T.ask(ri[xx])-T.ask(le[xx]-1);
            j++;
        }
    }

    sort(qq+1,qq+Q+1,cmp2);
    for(i=1;i<=Q;i++) printf("%d\n",qq[i].ans);
}

原文地址:https://www.cnblogs.com/dedicatus545/p/8907400.html

时间: 2024-10-14 05:02:55

[NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]的相关文章

BZOJ 2434: [Noi2011]阿狸的打字机( AC自动机 + DFS序 + 树状数组 )

一个串a在b中出现, 那么a是b的某些前缀的后缀, 所以搞出AC自动机, 按fail反向建树, 然后查询(x, y)就是y的子树中有多少是x的前缀. 离线, 对AC自动机DFS一遍, 用dfs序+树状数组维护, DFS到的查询点就回答询问.时间复杂度O(|ACAM|+QlogQ) ------------------------------------------------------------------------------------------- #include<cstdio>

[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组

[NOI2011]阿狸的打字机 Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的: l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后).l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失.l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失.例如,阿狸输入aPaPBbP

【BZOJ2434】【NOI2011】阿狸的打字机 AC自动机

转载请注明出处233:http://blog.csdn.net/vmurder/article/details/42875307 这是一道神题. 首先我们需要建立AC自动机,然后再建个Fail树,之后发现 如果询问a串在b串中出现了几次,那么只需要看b串在AC自动机上所有的节点中有多少个节点,在a串的结束节点在Fail树上的子树中就可以了. 然后这样做就很可以了,但是仍然不能AC, 这时我们只需要按照Fail树的dfs序建立数据结构(我写了树状数组)进行区间查询就好了. 这时对于以上的b串,我们

CodeForces 547E:Mike and Friends(AC自动机+DFS序+主席树)

What-The-Fatherland is a strange country! All phone numbers there are strings consisting of lowercase English letters. What is double strange that a phone number can be associated with several bears! In that country there is a rock band called CF con

[BZOJ2434]NOI2011阿狸的打字机|AC自动机|fail树|树状数组

这题真是太神了,好多实用的技巧..首先肯定是要先把每个要输出的串当模式串把自动机给建出来的,如果一个一个串复制出来再一个个插入显然非常慢...我们用在自动机上插入模式串的方法来建,初始时在0,新加一个字符就想下爬(或者新建),维护一个父亲指针,删除的时候就可以爬上去,这样就可以O(n)建出来了.. 再考虑询问的问题,每次把串拿出来再放进自动机跑一遍显然太慢..这里需要用到一个叫做fail树的东西,就是把fail指针当做边建成的一颗树..比如fail(i)=j,那么i在fail树上的父节点就是j.

BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Status][Discuss] Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的:l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最

【BZOJ-2434】阿狸的打字机 AC自动机 + Fail树 + DFS序 + 树状数组

2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2022  Solved: 1158[Submit][Status][Discuss] Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的:l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最

【BZOJ】2434: [Noi2011]阿狸的打字机 AC自动机+树状数组+DFS序

[题意]阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后). l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失. l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失. 我们把纸上打印出来的字符串从1开始顺序编号,一直到n.打字机有一个非

【bzoj2434】阿狸的打字机-AC自动机+fail树+优化

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=23083 Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后). l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失. l 按一下印有'P'的