[BZOJ2434][NOI2011]阿狸的打字机-AC自动机

阿狸的打字机

Description

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和’B’、’P’两个字母。

经阿狸研究发现,这个打字机是这样工作的:
l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
l 按一下印有’B’的按键,打字机凹槽中最后一个字母会消失。
l 按一下印有’P’的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

例如,阿狸输入aPaPBbP,纸上被打印的字符如下:

a
aa
ab

我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。

阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

Input

输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数m,表示询问个数。
接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。

Output

输出m行,其中第i行包含一个整数,表示第i个询问的答案。

Sample Input

aPaPBbP
aPaPBbP
3
1 2
1 3
2 3

Sample Output

2
1
0

HINT

1<=N<=10^5
1<=M<=10^5
输入总长<=10^5


1WA,原因:输出了打印次数个答案,而不是询问次数个答案,结果要么答案少一部分,要么多一堆迷之0……
还有这种操作……

(╯‵□′)╯︵┻━┻


思路:
首先,仔细观察题面,可以发现这个字符串在每个时间的状态都可以用一个栈表示。
那么:
插入一个字符等于在栈顶节点所在AC自动机上位置后插入一个儿子。
删除一个字符等于栈顶-1
打印所有字符等于将栈顶标记为一个字符串的结尾
然后咱就不需要把每个打印串分别求出并插入了~

然后,参见BZOJ3172,考虑使用fail树。
那么咱先对fail树求一遍dfs序,答案便是每个x节点的dfs区间中有多少个属于y的节点,这里属于y的节点的定义为,y串在AC自动机上的每个节点沿fail树走到根的路径上途径的所有点。

很显然,在dfs序上x节点的子树是一段连续的区间。
那么考虑再次模拟所有操作(其实就是对AC自动机进行dfs),使用一个树状数组维护。
每加入一个字符,就把在它dfs序上起点位置处给树状数组加上1。
每删除一个字符,就把刚才加的1减掉。
每遇到一次打印,那么便处理当前节点作为y时的所有询问:直接在树状数组上,对每个询问的x,查询其dfs序区间中的和为多少。
最后得到的查询结果即为在x子树中属于y节点的数量。
这就是答案了~

#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

const int N=1e5+9;

int n,m,t,ans[N],dfn;
int bit[N],stk[N],top;
char operate[N];
int to[N],nxt[N],id[N],beg[N],tot;

inline void push(int u,int v,int w)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    id[tot]=w;
    beg[u]=tot;
}

inline int lowbit(int x)
{
    return x&(-x);
}

inline void add(int x,int v)
{
    while(x<=dfn)
    {
        bit[x]+=v;
        x+=lowbit(x);
    }
}

inline int query(int x)
{
    int ret=0;
    while(x)
    {
        ret+=bit[x];
        x-=lowbit(x);
    }
    return ret;
}

struct AC_automaton
{
    int ch[N][28],fail[N],end[N],pool;
    int q[N],l,r,pos[N],st[N],ed[N];
    vector<int> g[N];

    inline int insert(int now,char c)
    {
        if(!ch[now][c-'a'])
            ch[now][c-'a']=++pool;
        return ch[now][c-'a'];
    }

    inline void set_end(int now,int id)
    {
        end[now]=id;
    }

    inline void getfail()
    {
        l=0;
        q[r=1]=0;
        fail[0]=0;

        while(l<r)
        {
            int u=q[++l];
            for(int i=0;i<26;i++)
                if(ch[u][i])
                {
                    q[++r]=ch[u][i];
                    fail[ch[u][i]]= u==0?0:ch[fail[u]][i];
                    g[fail[ch[u][i]]].push_back(ch[u][i]);
                }
                else
                    ch[u][i]= u==0?0:ch[fail[u]][i];
        }
    }

    inline void dfs(int u)
    {
        pos[u]=++dfn;
        if(end[u])
            st[end[u]]=dfn;
        for(int i=0,e=g[u].size();i<e;i++)
            dfs(g[u][i]);
        if(end[u])
            ed[end[u]]=dfn;
    }
}koishi;

int main()
{
    scanf("%s",operate+1);
    t=strlen(operate+1);

    stk[top=1]=0;
    for(int i=1;i<=t;i++)
        switch(operate[i])
        {
            case 'B':
                top--;
            break;
            case 'P':
                koishi.set_end(stk[top],++n);
            break;
            default:
                stk[++top]=koishi.insert(stk[top-1],operate[i]);
            break;
        }

    koishi.getfail();
    koishi.dfs(0);

    scanf("%d",&m);
    for(int i=1,x,y;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        push(y,x,i);
    }

    stk[top=1]=0;
    for(int i=1;i<=t;i++)
        switch(operate[i])
        {
            case 'B':
                add(koishi.pos[stk[top]],-1);
                stk[top--]=0;
            break;
            case 'P':
                for(int i=beg[koishi.end[stk[top]]];i;i=nxt[i])
                    ans[id[i]]=query(koishi.ed[to[i]])-query(koishi.st[to[i]]-1);
            break;
            default:
                stk[++top]=koishi.ch[stk[top-1]][operate[i]-'a'];
                add(koishi.pos[stk[top]],1);
            break;
        }

    for(int i=1;i<=m;i++)
        printf("%d\n",ans[i]);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值