P2414 [NOI2011]阿狸的打字机

六眼飞鱼酱① 提交于 2019-12-02 12:08:24

题意:

打字机上只有28个按键,分别印有26个小写英文字母和’B’、'P’两个字母。经阿狸研究发现,这个打字机是这样工作的:

·输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。

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

·按一下印有’P’的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

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

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

题解:

字符串总长1e5,如果我们在线做法,肯定会T到爆炸,因为每次询问都需要插入重建fail图,所以我们选择离线处理,把所有查询y中有多少个x的询问一起查询,即查询y中有多少个x1,x2,xn…,把所有操作形成的字符串全部放进去,但这样显然还是会超时,怎么办?

仔细想想,y中有多少个x,不就是求y的某个节点往上跳fail能不能到达x,因为每个节点的fail一定最多只有一条,所有我们完全可以反向建边,建出fail树,那么我们的问题不就是求x往下能到达多少个y的节点,那这就不是相当于求子树和了吗?

再想想,如果把所有y的节点全部赋一个权值1,那么不就是求x的最后一个节点的子树和吗?
这样交上去,又T,真是毒瘤…

那么我们还能怎么优化呢?我们每次求子树和的时候都是暴力求和,时间复杂度确实过不了,但没关系,求子树和,我们完全可以用线段树或者树状数组优化!想想将树上的点按照dfs序打上标号后,一个树的子树和就相当于被降维打击了,完全可以看成一个数组,这样就可以简单的套模板求和啦!我们只需要每次在B操作时,将该点的权值-1,移动root到它的父节点。每次访问到一个结束的节点时,一定是有且仅有这个串的节点被标记,这样就能回答关于这个串的相关询问啦!
具体细节见代码!

由于本蒟蒻线段树容易写崩掉,所以选择了好写的树状数组。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+50;
struct Trie{
    int nxt[MAXN][26],fail[MAXN],fa[MAXN],end[MAXN];
    int sz[MAXN],dfn[MAXN],tree[MAXN],ans[MAXN];
    int tot,n,m,stot,cnt;
    char s[MAXN];
    vector<int> g[MAXN];
    vector<pair<int,int> > v[MAXN];
    void insert(){
        int root=0;
        for(int i=1;i<=n;i++){
            if(s[i]=='P') end[++stot] = root;//记录第stot个字符串的结束节点是root
            else if(s[i]=='B') root = fa[root];
            else{
                if(!nxt[root][s[i]-'a']) nxt[root][s[i]-'a'] = ++tot,fa[tot] = root;
                root = nxt[root][s[i]-'a'];
            }
        }
    }
    void build(){//建立fail树
        queue<int> que;
        for(int i=0;i<26;i++)
            if(nxt[0][i])
                g[0].push_back(nxt[0][i]),que.push(nxt[0][i]);//指向0的节点一定要加进去!!
        while(!que.empty()){
            int u = que.front();
            que.pop();
            for(int i=0;i<26;i++){
                if(nxt[u][i]){
                    int root = fail[u];
                    while(root && !nxt[root][i]) root = fail[root];
                    que.push(nxt[u][i]);
                    fail[nxt[u][i]] = nxt[root][i];
                    g[nxt[root][i]].push_back(nxt[u][i]);
                }
            }
        }
    }
    void dfs(int u){//dfs序建树
        dfn[u] = ++cnt;
        sz[u] = 1;
        for(int i=0;i<(int)g[u].size();i++){
            int v = g[u][i];
            dfs(v);
            sz[u] += sz[v];//求出子树的点数
        }
    }
    int lowbit(int x) { return x & -x; }//简单好写的树状数组
    void add(int x,int val){
        for(int i=x;i<=cnt;i+=lowbit(i)) tree[i] += val;
    }
    int query(int x){
        int res = 0;
        for(int i=x;i;i-=lowbit(i)) res += tree[i];
        return res;
    }
    void solve(){
        int root = 0,cont = 0;
        for(int i=1;i<=n;i++){
            if(s[i]=='P'){
                cont++;//遍历第cont个字符串中是否有问题要解决
                for(int j=0;j<(int)v[cont].size();j++){
                    int x = v[cont][j].first;
                    ans[v[cont][j].second] += query(dfn[end[x]] + sz[end[x]] - 1) - query(dfn[end[x]] - 1);
                    //求子树和
                }
            }
            else if(s[i]=='B'){
                add(dfn[root],-1);//该点权值-1
                root = fa[root];
            }
            else{
                root = nxt[root][s[i]-'a'];
                add(dfn[root],1);
            }
        }
    }
    void work(){
        scanf("%s%d",s+1,&m);
        n = strlen(s+1);
        for(int i=1;i<=m;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            v[y].push_back(make_pair(x,i));
            //y中有多少个x,这个是第i个问题
        }
        insert();
        build();
        dfs(0);
        solve();
        for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    }
}Automaton;
int main(){
    Automaton.work();
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!