嘘~ 正在从服务器偷取页面 . . .

CF710F String Set Queries 题解


CF710F String Set Queries 题解

题目链接:CF710F String Set Queries

题意

维护一个字符串集合,支持三种操作:

  1. 加字符串
  2. 删字符串
  3. 查询集合中的所有字符串在给出的模板串中出现的次数

操作数 \(m \leq 3 \times 10^5\),输入字符串总长度 \(\sum |s_i| \leq 3\times 10^5\)

本题强制在线,应该在每次输出后调用fflush(stdout)

你只有在输出上一个询问的答案后才能读入下一组询问。

过了好几天了才开始写题解,着实有些懒。(最近两只手都腱鞘炎了)

这道题就是 二进制分组 + AC 自动机 ,下面我们来分析这个想法是怎么来的。

先考虑暴力,我们每次删除的时候都对原字符串进行重构,显然这是一种低效的方式。

这个其实很好解决。由于贡献是可以加减的,所以我们考虑把删除操作改为在另一个 Trie 上面插字符串

我们称原先的 Trie 是 Add 树,另外的是 Sub 树,那么答案就是 Add 树的贡献减 Sub 树的贡献。

思考一下这样的时间复杂度是多少:最坏情况下,字符长度一定为 \(1,2,3,\cdots,k\) ,且 \(k \le \sqrt{2\,\mathrm{len}}\)

其中 \(\mathrm{len} = \sum|s_i|\) ,故不做其他优化的话,时间复杂度是 \(\mathcal{O}(\mathrm{len}^2)\)

于是接下来就有两种处理方法:

  • 第一种,我们只需要枚举集合中比当前串长度小的串即可,此时时间复杂度是 \(\mathcal{O}(\mathrm{len}\sqrt{\mathrm{len}})\)
  • 第二种,也就是我们前面提到的二进制分组。

第一种方法确实是很妙,如果没懂可以参考这篇文章,不过本文要讲第二种方法。

我们考虑对每次插入,都新建一个 Trie ,记它的大小为 \(1\) ,那么我们要维护形如 \(1,2,4,8,16,\cdots\) 若干棵 Trie

什么意思呢?比如我们现在有大小为 \(1,2,8\) 三棵 Trie ,此时的操作是插入,我们新建了一个大小为 \(1\) 的 Trie

此时我们有 \(1,1,2,8\) 四棵 Trie ,然后我们直接把相同大小的合并,就像游戏 2048 一样,最终得到 \(4,8\) 两棵 Trie

考虑这样的操作对复杂度有什么影响:

每次合并,就是把构造其中一棵树的所有字符串插到另一棵里,然后删掉这棵树。

而 AC 自动机的构造过程是线性的,因此时间复杂度为 \(\mathcal{O}(\mathrm{len}\log n)\)

写的好看一点,就是 \(\mathcal{O}(\sum|s_i|\log n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(3e5 + 15))

struct Trie
{
    string data[N];
    int n, tot, ch[N][26], ed[N], fail[N];
    void insert(int rt, char *s)
    {
        int u = rt, len = strlen(s + 1);
        for(int i = 1; i <= len; i++)
        {
            int c = s[i] - 'a';
            if(ch[u][c] == rt) {
                ch[u][c] = ++tot;
                for(int j = 0; j < 26; j++) ch[tot][j] = rt;
            }
            u = ch[u][c];
        }
        ++ed[u];
    }
    void build(int rt)
    {
        fail[rt] = rt; queue<int> q;
        for(int i = 0; i < 26; i++)
            if(ch[rt][i] > rt) { q.push(ch[rt][i]), fail[ch[rt][i]] = rt; }
        while(!q.empty())
        {
            int u = q.front(); q.pop();
            for(int i = 0; i < 26; i++)
            {
                if(ch[u][i] != rt)
                {
                    fail[ch[u][i]] = ch[fail[u]][i];
                    ed[ch[u][i]] += ed[ch[fail[u]][i]]; q.push(ch[u][i]);
                }
                else ch[u][i] = ch[fail[u]][i];
            }
        }
    }
    int search(int u, char *s)
    {
        int res = 0, len = strlen(s + 1);
        for(int i = 1; i <= len; i++)
        {
            int c = s[i] - 'a';
            u = ch[u][c]; res += ed[u];
        }
        return res;
    }
    int top,stk[N],fr[N],sz[N];
    void merge()
    {
        --top; sz[top] *= 2;
        for(int i = stk[top]; i <= tot; i++)
        {
            ed[i] = fail[i] = 0;
            for(int j = 0; j < 26; j++) ch[i][j] = 0;
        }
        tot = stk[top];
        for(int i = 0; i < 26; i++) ch[tot][i] = tot;
        for(int L = fr[top]; L <= n; L++) insert(stk[top], &data[L][0]);
        build(stk[top]);
    }
    void Ins(char *s)
    {
        stk[++top] = ++tot;
        for(int i = 0; i < 26; i++) ch[tot][i] = tot;
        sz[top] = 1; insert(tot, s); build(stk[top]);
        int len = strlen(s + 1); data[fr[top] = ++n] = " ";
        for(int i = 1; i <= len; i++) data[n] += s[i];
        while(sz[top] == sz[top - 1]) merge();
    }
    int Count(char *s)
    {
        int res = 0;
        for(int i = 1; i <= top; i++)
            res += search(stk[i], s);
        return res;
    }
}Add, Sub;
char tmp[N];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int Q; for(cin >> Q; Q--; )
    {
        int op; cin >> op >> (tmp + 1);
        if(op == 1) Add.Ins(tmp); else if (op == 2) Sub.Ins(tmp);
        else cout << Add.Count(tmp) - Sub.Count(tmp) << '\n'; cout.flush();
    }
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/blog/52881/ti-xie-cf710f-string-set-queries


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录