BZOJ 2434 阿狸的打字机 (AC自动机 + 树状数组)

题目大意:

中文题面, 就是在解密串之后询问第x行的串在第y行的串中出现了几次

串的最大长度 <= 10W, 询问数 <= 10W


大致思路:

首先不难将给出的串解出来插入到Trie树中, 那么对于每一次询问, 其实就是在对于所有串建立AC自动机之后, 考虑AC自动机的fail树, 第x行的字符串的结尾位置的结点是u的话, 就相当于问在fail树中, 以u为根的子树下有多少个结点是AC自动机上第y行的字符串经过的点

于是考虑到离线算法, 首先将所有的询问按照y相同, 这里理解为y对应的AC自动机上的结点相同, 将这些询问分在一组

然后对于fail树的时间戳用树状数组维护

从根节点开始dfs, 每次经过一个点就将这个点对应的时间戳上的位置+1, 离开这个点就-1, 这样的话dfs序在到达y的结点的时候, +1的区间位置一定是y串所经过的点, 那么就相当于标记了这些点是1其他的都是0, 于是用树状数组询问以x为根的子树下当前被标记的点的个数就是其时间戳对应的区间和了, 于是离线的做法时间复杂度是O(n + mlogn)


代码如下:

Result  :  Accepted     Memory  :  31312 KB     Time  :  720 ms

/*
 * Author: Gatevin
 * Created Time:  2015/10/21 15:06:29
 * File Name: Sakura_Chiyo.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

#define maxn 100010

char in[maxn];

struct Ask
{
    int x, y, id;
    Ask(){}
    Ask(int _x, int _y, int _id) : x(_x), y(_y), id(_id){}
};
Ask ask[100010];

int pos[100010];
int result[100010];
vector<Ask> Query[maxn];

struct BIT//单点修改, 区间查询
{
    int C[maxn];
    int N;
    int lowbit(int x)
    {
        return -x & x;
    }
    void add(int x, int val)
    {
        while(x <= N) C[x] += val, x += lowbit(x);
    }
    int sum(int x)
    {
        int ret = 0;
        while(x) ret += C[x], x -= lowbit(x);
        return ret;
    }
    void init()
    {
        memset(C, 0, sizeof(C));
    }
    BIT(){}
};

BIT bit;

struct Trie
{
    int next[maxn][26], fail[maxn], par[maxn];//par代表每个节点在Trie树上的父亲节点
    int trie_next[maxn][26];
    //建立AC自动机之后next数组会改变, 为了还原将建立AC自动机前原本的Trie储存在trie_next中
    int L, root;
    vector<int> G[maxn];//fail 树
    int newnode()
    {
        for(int i = 0; i < 26; i++)
            next[L][i] = -1;
        L++;
        return L - 1;
    }
    void init()
    {
        L = 0;
        root = newnode();
        par[root] = root;
    }
    int insert(int now, deque<char> &Q, int cnt)
    {
        while(!Q.empty())
        {
            int go = Q.front() - 'a';
            Q.pop_front();
            if(next[now][go] == -1)
            {
                next[now][go] = newnode();
                par[next[now][go]] = now;
            }
            now = next[now][go];
        }
        pos[cnt] = now;//记录每一个被询问的字符串的结尾位置编号
        return now;
    }
    void construct(int len)
    {
        deque<char> Q;
        int now = root;
        int cnt = 1;
        int i = 0;
        while(i < len)
        {
            if(!Q.empty()) Q.clear();
            while(i < len && in[i] != 'P')
            {
                if(in[i] == 'B')
                {
                    if(Q.empty())
                        now = par[now];
                    else Q.pop_back();
                }
                else
                   Q.push_back(in[i]); 
                i++;
            }
            if(in[i] == 'P')
            {
                i++;
                now = insert(now, Q, cnt++);
            }
        }
        return;
    }
    
    void save()//保留住原来的trie
    {
        for(int i = 0; i < L; i++)
            for(int j = 0; j < 26; j++)
                trie_next[i][j] = next[i][j];
    }
    
    void build()//建立AC自动机
    {
        fail[root] = root;
        queue<int> Q;
        Q.push(root);
        while(!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            for(int i = 0; i < 26; i++)
            {
                if(next[now][i] == -1)
                    next[now][i] = now == root ? root : next[fail[now]][i];
                else
                {
                    fail[next[now][i]] = now == root ? root : next[fail[now]][i];
                    Q.push(next[now][i]);
                }
            }
        }
        return;
    }
    int tim, left[maxn], right[maxn];//时间戳用
    void dfs(int now)
    {
        left[now] = ++tim;
        for(int i = 0, sz = G[now].size(); i < sz; i++) 
                dfs(G[now][i]);
        right[now] = tim;
    }
    
    void answer(int now)
    {
        bit.add(left[now], 1);
        if(!Query[now].empty())
            for(int i = 0, sz = Query[now].size(); i < sz; i++)
            {
                if(pos[Query[now][i].x] == root || pos[Query[now][i].y] == root) result[Query[now][i].id] = 0;
                result[Query[now][i].id] =  bit.sum(right[pos[Query[now][i].x]]) - bit.sum(left[pos[Query[now][i].x]] - 1);
            }
        for(int i = 0; i < 26; i++)
            if(trie_next[now][i] != -1)
                answer(trie_next[now][i]);
        bit.add(left[now], -1);
    }
    
    void solve()
    {
        init();
        memset(pos, 0, sizeof(pos));
        for(int i = 0; i < maxn; i++) Query[i].clear(), G[i].clear();
        gets(in);
        int len = strlen(in);
        construct(len);
        int m;
        scanf("%d", &m);
        for(int i = 0; i < m; i++)
        {
            scanf("%d %d", &ask[i].x, &ask[i].y);
            ask[i].id = i;
            Query[pos[ask[i].y]].push_back(ask[i]);
        }
        save();
        build();
        for(int i = 1; i < L; i++)//找出fail树
            G[fail[i]].push_back(i);
        tim = 0;
        dfs(root);//建立fail树的时间戳区间
        bit.N = tim + 1;
        bit.init();//初始化树状数组
        answer(root);//遍历自动机利用树状数组完成询问
        for(int i = 0; i < m; i++)
            printf("%d\n", result[i]);
    }
};

Trie ac;

int main()
{
    ac.solve();
    return 0;
}

/*

aPaPBbP
3
1 2
1 3
2 3

*/


你可能感兴趣的:(树状数组,AC自动机,bzoj,阿狸的打字机,2434)