zoukankan      html  css  js  c++  java
  • Codeforces 710F String Set Quries

    题意

    维护一个字符串的集合$D$, 支持3种操作:

    1. 插入一个字符串$s$
    2. 删除一个字符串$s$
    3. 查询一个字符串$s$在$D$中作为子串出现的次数

    强制在线

    解法

    AC自动机+二进制分组

    二进制分组

    二进制分组是一种用 (套用) 离线方法解决要求强制在线问题的分块技巧. 我第一次见到它是在2013年IOI国家集训队许昊然的论文<浅谈数据结构题的几个非经典解法>中. 满足修改操作对询问的贡献独立, 修改操作之间互不影响效果 (其实前后两句说的是同一件事) 的数据结构题, 都可以采用二进制分组算法.

    原理

    修改操作序列按二进制分组. 所谓"二进制", 指的是将长为$n$的修改序列按原顺序分成$k$组 (实际上是对时间分块), $k$为$n$的二进制表示中1的数目, 第$i$组的长度为第$i$个1的权重 (2的幂), 每组用一个数据结构维护. 例如, 长为10的操作序列将分成两组长度分别为 8 (1~8), 2 (9~10). 当修改序列的长度从$n$变成$n+1$时, 暴力重建变化的那些组, 不难看出需要重建的修改序列的总长度为lowbit($n+1$).

    不难看出, 这个题目所涉及的操作与询问满足上述条件.

    实现

    二进制分组的框架:
    设修改序列为vector<operation> s, 数据结构序列为 vector<structure> t.

    1. pop_back: 将过期的分组从队尾弹出
    for(x=S.size(); x && lowbit(x)<lowbit(S.size()+1); t.pop_back(), x-=lowbit(x));
    s.push_back(cur_op);    // 将当前修改操作加进修改序列
    
    1. push_back: 将新建分组入队
    structure cur;    // 初始化为空
    for(int i=s.size()-lowbit(s.size()); i<s.size(); i++)    // 0-indexed
        cur.insert(s[i]);
    t.push_back(cur);
    

    Implementation

    本题中除了用到二进制分组意外, 还有一个巧妙的转化:

    分别维护插入和删操作形成的AC-自动机组 X, Y (即把删除操作也当作插入操作), 最后结果就是在X中查询的答案减去在Y中查询的答案.

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N{1<<19}, M{1<<10};
    typedef long long LL;
    
    vector<string> s;
    
    struct node{
        int ch[26], fail, last, cnt;
        bool f;
    };
    
    int lowbit(int x){
        return x&-x;
    }
    
    queue<int> que;
    
    struct AC{
        using trie = vector<node>;
        vector<trie> g; //group
        vector<string> s;   //string buffer
    
        void insert(const string &t){
            for(int x=s.size(); x && lowbit(x)<lowbit(s.size()+1); g.pop_back(), x-=lowbit(x));    //error-prone
            g.push_back({});
            auto &cur=*g.rbegin();
            cur.push_back({});
            s.push_back(t);
    
            for(int i=s.size()-lowbit(s.size()); i<s.size(); i++){
                int u=0;
                for(auto &x:s[i]){
                    // int &v=cur[u].ch[x-'a'];    //error
                    // do not use a reference to an object stored in a vector or any other dynamically allocated container,
                    // when it is under construction.
                    if(!cur[u].ch[x-'a']){
                        cur[u].ch[x-'a']=cur.size();
                        cur.push_back({});
                    }
                    u=cur[u].ch[x-'a'];
                }
                cur[u].cnt=1;
                cur[u].f=true;
            }
    
            for(int i=0; i<26; i++){
                int u=cur[0].ch[i];
                if(u) que.push(u);
            }
    
            for(; que.size(); ){
                int u=que.front();
                que.pop();
                for(int i=0; i<26; i++){
                    int &v=cur[u].ch[i];    //error-prone
                    if(v){  //v is a new node
                        que.push(v);
                        int &fail=cur[v].fail, &last=cur[v].last;
                        fail=cur[cur[u].fail].ch[i];
                        last=cur[fail].f ? fail : cur[fail].last;
                        cur[v].cnt+=cur[last].cnt;
                        // alternative: cur[v].cnt+=cur[fail].cnt;
                    }
                    else v=cur[cur[u].fail].ch[i];
                }
            }
        }
    
        LL match(const string &t){
            LL res=0;
            for(auto &cur: g){
                int u=0;
                for(auto &x: t){    // no need to add a const before auto
                    u=cur[u].ch[x-'a'];
                    res+=cur[u].cnt;
                }
            }
            return res;
        }
    }a, b;
    
    
    
    
    
    int main(){
        int m, tail=0;
        cin>>m;
        string x;
        for(int i=0, t; i<m; i++){
            cin>>t>>x;
            if(t==1){
                a.insert(x);
            }
            else if(t==2){
                b.insert(x);
            }
            else cout<<a.match(x)-b.match(x)<<endl;
        }
    }
    

    代码中注释了我当时犯的一个不易察觉的错误.

  • 相关阅读:
    为了抓包某APP所做的尝试(to be continued)
    VirtualBox的使用的一些Tips 网络配置|硬盘扩充
    斜线和反斜线简要历史,为什么windows和unix采用不同的路径分隔符
    求出二维数组主对角线、次对角线以及周边元素之和
    C#计算两个时间的时间差,精确到年月日时分秒
    C#获取MP3,WMA信息
    C#窗体随意移动
    DEV GridControl小结
    DEV 皮肤的使用
    C#窗体阴影
  • 原文地址:https://www.cnblogs.com/Patt/p/6110253.html
Copyright © 2011-2022 走看看