P2414 [NOI2011]阿狸的打字机(AC自动机,dfs序,离线,优化建图)

传送门
这是一道练习AC自动机的好题.先来考虑一下题目特殊的读入方式.
如果把每个字符串存起来一个一个插入AC自动机的话这种做法最多只能过四个点.因为存储字符串的空间复杂度是O(|S|2)的.S的最大长度在1e5.这显然是不可接受的.

优化一下建Trie树的过程.可以观察到这种生成字符串的方式会产生很多重复的结点.所以我们直接在读入的时候跳Trie树的指针就可以了,在接收到B的时候跳回p的父亲节点就可以了.接收到正常字符就正常跳指针.

再来考虑一下怎么处理询问.先假定我们读入的字符串是正常一个一个读入的,那只需要遍历一遍y串,然后每次暴力跳fail指针打标记,最后看x的节点被打了几次标记就可以了.

这种方法显然太暴力了,我们知道暴力跳fail的过程都可以在fail指针上形成的树上转化成为树上的一些操作问题.如果对这种做法不了解的可以先去写一下洛谷上面的AC自动机(二次加强版).

所以我们考虑怎么在fail树上回答问题.还是假定一下读入的字符串是正常一个一个读入的.那么可以把暴力跳fail指针的过程转化为单点修改和子树求和的问题.只需要用dfs序+树状数组即可.如果这题不是这种特殊的读入方式的话,这种做法应该是可行的.复杂度是O(nlogn)的.

正解:可以观察到的是我们对fail树的修改其实是一个dfs的过程.而在每个询问里,是修改到y所在的结点停止.所以我们无需知道每一个字符串具体是怎么样的.只要把询问离线,给每个y在树上打标记,然后批量处理这个点的询问即可.这里需要搞清楚的是trie树和fail树这两颗树上操作的区别.以及各种映射之间的关系
参考代码:

#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#include <bits/stdc++.h>

using namespace std;
const int N = 4e5+10;

inline int read(){
    int x = 0,f=1;char ch = getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace Bit{ // 单点修改,区间求和
    int n,c[N];
    void add(int p,int v){
        if(!p) return;
        for(;p<=n;p+=p&-p) c[p] += v;
    }
    int ask(int p){
        int res = 0;
        for(;p;p-=p&-p) res += c[p];
        return res;
    }
    int query(int l,int r){
        return ask(r)-ask(l-1);
    }
}
namespace Auto{ 
    int trie[26][N],tot,p,fail[N],fa[N],cnt[N],bel[N],tt[26][N],ttot;
    vector<pii> vec[N];
    void bfs(){
        queue<int> q;
        fir(i,0,25) if(trie[i][0]) q.push(trie[i][0]);
        while(q.size()){
            int p = q.front(),fl = fail[p];
            q.pop();
            fir(i,0,25){
                if(trie[i][p]) fail[trie[i][p]] = trie[i][fl],q.push(trie[i][p]);
                else trie[i][p] = trie[i][fl];
            }
        }
    }
    int tim,dfn[N],nxt[N],head[N],to[N],ct,ed[N],ans[N];
    void addedge(int x,int y){
        nxt[++ct] = head[x];head[x] = ct;to[ct] = y;
    }
    void build(){
        afir(i,tot,1) addedge(fail[i],i);
    }
    void dfs(int u){
        dfn[u] = ++tim;
        for(int i=head[u];i;i=nxt[i]){
            int y = to[i];
            dfs(y);
        }
        ed[u] = tim;
    }
    void solve(int u){
        Bit::add(dfn[u],1);
        for(auto x:vec[u])
            ans[x.ft] = Bit::query(dfn[x.sd],ed[x.sd]);
        fir(i,0,25){
            if(tt[i][u])
                solve(tt[i][u]);
        }
        Bit::add(dfn[u],-1);
    }
}
string s[N];
using namespace Auto;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    string str;
    cin >> str;
    p = 0;
    int n = 0;
    for(auto x:str){
        if(x == 'B')
            p = fa[p];
        else if(x == 'P') bel[++n] = p;
        else{
            if(!trie[x-'a'][p]){
                fa[++tot] = p;
                trie[x-'a'][p] = tot;
                tt[x-'a'][p] = tot;
            }
            p = trie[x-'a'][p];
        }
    }
    bfs();
    build();
    dfs(0);
    Bit::n = tim;
    int m;
    cin >> m;
    fir(i,1,m){
        int x,y;
        cin >> x >> y;
        vec[bel[y]].pb(mpr(i,bel[x]));
    }
    Auto::solve(0);
    fir(i,1,m) cout << ans[i] << "\n";
    
    return 0;
}    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值