【NOI2011】阿狸的打字机

6 篇文章 0 订阅
3 篇文章 0 订阅

Description:

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

输入小写字母,打字机的一个凹槽中会加入这个字母(按 P 前凹槽中至少有一个字母)。 

按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。 

按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母) 。 

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

a

aa

ab

我们把纸上打印出来的字符串从 1开始顺序编号,一直到 n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。 阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

题解:

这题不如之前那题可以直接用其它的高级数据结构搞出来。

这题只可以AC自动机了。

还是之前那题的套路。

这样就相当于y在trie上的每一个前缀往fail跳多少个x。

反过来就是x往fail的子树跳有多少个是y的前缀。

这题显然可以离线。

先对fail树建dfs序,因为这样子树会在连续的一段里。

把询问打在y上,然后按trie的顺序走,询问就是了。

可以用树状数组或线段树维护。

Code:

#include<cstdio>
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define low(x) ((x) & -(x))
using namespace std;

const int N = 1e5 + 5;

int n, m, x, tot = 1, son[N][26], fa[N], num[N], nt; char str[N];

struct Ask {
    int x, y;
} a[N];

struct edge {
    int final[N], next[N], to[N], pq[N], tt;
    void link(int x, int y, int z) {
        next[++ tt] = final[x], to[tt] = y, pq[tt] = z, final[x] = tt;
    }
} e, ac;

int ans[N];

int dfn[N], td, l[N], r[N], f[N];

void add(int x, int y) {
    while(x <= tot) f[x] += y, x += low(x);
}

int find(int x) {
    int s = 0;
    while(x) s += f[x], x -= low(x);
    return s;
}

void dfs(int x) {
    dfn[x] = l[x] = ++ td;
    for(int i = ac.final[x]; i; i = ac.next[i]) {
        int y = ac.to[i]; dfs(y);
    }
    r[x] = td;
}

void dg(int x) {
    add(dfn[x], 1);
    fo(j, 0, 25) if(son[x][j])
        dg(son[x][j]);
    for(int i = e.final[x]; i; i = e.next[i]) {
        int y = e.to[i], z = e.pq[i];
        ans[z] = find(r[y]) - find(l[y] - 1);
    }
    add(dfn[x], -1);
}

int fail[N], d[N];

void Build_ac() {
    fo(i, 0, 25) son[0][i] = 1;
    d[0] = d[1] = 1;
    fo(i, 1, d[0]) {
        int x = d[i];
        fo(j, 0, 25) if(son[x][j]) {
            int y = son[x][j], z = fail[x];
            while(!son[z][j]) z = fail[z];
            fail[y] = son[z][j]; d[++ d[0]] = y;
        }
    }
    fo(i, 1, tot) ac.link(fail[i], i, 0);
}

int main() {
    scanf("%s", str + 1); m = strlen(str + 1);
    x = 1;
    fo(i, 1, m) {
        int c = str[i];
        if(c == 'B') {
            x = fa[x];
        } else
        if(c == 'P') {
            num[++ nt] = x;
        } else {
            c = c - 'a';
            if(!son[x][c]) fa[++ tot] = x, son[x][c] = tot;
            x = son[x][c];
        }
    }
    Build_ac();
    scanf("%d", &m);
    fo(i, 1, m) {
        scanf("%d %d", &a[i].x, &a[i].y);
        e.link(num[a[i].y], num[a[i].x], i);
    }
    dfs(1); dg(1);
    fo(i, 1, m) printf("%d\n", ans[i]);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值