bzoj 2434: [Noi2011]阿狸的打字机

29 篇文章 0 订阅
3 篇文章 0 订阅

bzoj 2434: [Noi2011]阿狸的打字机

线段树,AC自动机应用

题意

初始字串为空,首先给定一系列操作序列,有三种操作:

  1. 在结尾加一个字符
  2. 在结尾删除一个字符
  3. 打印当前字串

然后多次询问第x个打印的字串在第y个打印的字串中出现了几次。

思路

%%%1
%%%2

操作字符串的过程实际是构建Trie的过程。新建字母相当于新加节点,打印字串相当于给标记,删除字母相当于回到爸爸节点。

因为Trie保存的是前缀,而子串相当于前缀的后缀,所以我们可以利用AC自动机的Fail指针:字符串u通过fail指针指向字符串v,表明v是u的子串。但是如果我们对于每个询问都直接处理,复杂度肯定过高。

又fail的性质是每个节点出度是1,所以反过来就是一棵树。u是v的爸爸意味着u代表的字串是v的子串。那么我们处理出fail树的dfs序,然后将询问离线按右端点排序,查询左端点的已经出现的fail树中的儿子个数即可。

维护一个线段树,对原Trie树进行遍历,每访问一个节点,就修改线段树,对线段树中该节点的DFS序起点的位置加上1。每往回走一步,就减去1。如果访问到了一个y字串的末尾节点,枚举询问中每个y串对应的x串,查询线段树中x串末尾节点从DFS序中的起始位置到结束位置的和,并记录答案。

然后。。。我他妈for(int i=0;i<strlen(s);i++)成功把复杂度优化到了n方,然后gg了一晚上。

代码

#include <bits/stdc++.h>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define M(a,b) memset(a,b,sizeof(a))
using namespace std;
const int MAXN=100007;
const int oo=0x3f3f3f3f;

char input[MAXN];
//AC自动机
struct AC
{
    static const int maxnode=100007;
    static const int sigma_size=26;
    int ch[maxnode][sigma_size], val[maxnode], fail[maxnode], fa[maxnode];
    int sz;//节点总数
    void init()
    {
        sz=1;M(ch[0], 0);
    }
    inline int getidx(char c) { return c-'a'; }
    void build()
    {
        int u=0, n=strlen(input);int tmp=0;
        for(int i=0;i<n;i++)
        {
            if(input[i]=='P') val[++tmp]=u;
            else if(input[i]=='B') u=fa[u];
            else
            {
                int c=getidx(input[i]);
                if(!ch[u][c])
                {
                    M(ch[sz], 0);
                    ch[u][c]=sz;
                    fa[sz++]=u;
                }
                u=ch[u][c];
            }
        }
    }
    void getFail()
    {
        queue<int> q;
        fail[0]=0;
        for(int c=0;c<sigma_size;c++)
        {
            int u=ch[0][c];
            if(u) { fail[u]=0;q.push(u); }
        }
        while(!q.empty())
        {
            int fr=q.front();q.pop();
            for(int c=0;c<sigma_size;c++)
            {
                int u=ch[fr][c];
                if(!u) continue;
                q.push(u);
                int v=fail[fr];
                while(v&&!ch[v][c]) v=fail[v];
                fail[u]=ch[v][c];
            }
        }
    }
}ac;

//线段树
int stree[MAXN<<2];
void pushup(int rt) { stree[rt]=stree[rt<<1]+stree[rt<<1|1]; }
void build() { M(stree, 0); }
void update(int pos, int v, int l, int r, int rt)
{
    if(l==r) { stree[rt]+=v;return; }
    int mid=(l+r)>>1;
    if(pos<=mid) update(pos, v, lson);
    else update(pos, v, rson);
    pushup(rt);
}
int query(int L, int R, int l, int r, int rt)
{
    if(L<=l&&r<=R) return stree[rt];
    int mid=(l+r)>>1;
    int res=0;
    if(L<=mid) res+=query(L, R, lson);
    if(R>mid) res+=query(L, R, rson);
    return res;
}

//fail树
int head[MAXN];
struct Edge
{
    int to, ne;
    Edge() {}
    Edge(int a, int b) { to=a, ne=b; }
};
Edge e[MAXN];
int sume=0;
void addedge(int u, int v)
{
    e[sume]=(Edge(v, head[u]));head[u]=sume;
    sume++;
}
int in[MAXN], ou[MAXN], dfsnum=0;
void dfs(int u, int fa)
{
    in[u]=++dfsnum;
    for(int i=head[u];~i;i=e[i].ne)
    {
        int v=e[i].to;
        if(v==fa) continue;
        dfs(v, u);
    }
    ou[u]=dfsnum;
}

//离线询问
int hq[MAXN];
struct Query
{
    int x, y;
    int ne;
    int res;
    void rd(int _i)
    {
        scanf("%d%d", &x, &y);
        res=0;ne=hq[y];
        hq[y]=_i;
    }
}q[MAXN];

void solve(int n)
{
    int curpos=0;int tmp=1;
    int len=strlen(input);
    for(int i=0;i<len;i++)
    {
        if(input[i]=='P')
        {
            for(int k=hq[tmp];~k;k=q[k].ne)
            {
                int nodenum=ac.val[q[k].x];
                q[k].res=query(in[nodenum], ou[nodenum], 1, n, 1);
            }
            tmp++;
        }
        else if(input[i]=='B')
        {
            update(in[curpos], -1, 1, n, 1);
            curpos=ac.fa[curpos];
        }
        else
        {
            curpos=ac.ch[curpos][input[i]-'a'];
            update(in[curpos], 1, 1, n, 1);
        }
    }
}

int main()
{
    scanf("%s", input);
    ac.init();sume=0;M(head, -1);build();M(hq, -1);
    dfsnum=0;

    ac.build();
    ac.getFail();

    for(int i=0;i<ac.sz;i++)
    {
        int u=i, v=ac.fail[i];
        if(u!=v) addedge(v, u);
    }
    dfs(0, -1);
    int n=dfsnum;

    int m;scanf("%d", &m);
    for(int i=1;i<=m;i++)
        q[i].rd(i);

    solve(n);

    for(int i=1;i<=m;i++)
    {
        printf("%d\n", q[i].res);
    }
    //system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值