【Codeforces710F】String Set Queries (强制在线)AC自动机 + 二进制分组

时间:2022-02-25 12:04:06

F. String Set Queries

time limit per test:3 seconds
memory limit per test:768 megabytes
input:standard input
output:standard output

You should process m queries over a set D of strings. Each query is one of three kinds:

  1. Add a string s to the set D. It is guaranteed that the string s was not added before.
  2. Delete a string s from the set D. It is guaranteed that the string s is in the set D.
  3. For the given string s find the number of occurrences of the strings from the set D. If some string p from D has several occurrences in s you should count all of them.

Note that you should solve the problem in online mode. It means that you can't read the whole input at once. You can read each query only after writing the answer for the last query of the third type. Use functions fflush in C++ and BufferedWriter.flush in Javalanguages after each writing in your program.

Input

The first line contains integer m (1 ≤ m ≤ 3·105) — the number of queries.

Each of the next m lines contains integer t (1 ≤ t ≤ 3) and nonempty string s — the kind of the query and the string to process. All strings consist of only lowercase English letters.

The sum of lengths of all strings in the input will not exceed 3·105.

Output

For each query of the third kind print the only integer c — the desired number of occurrences in the string s.

Examples

input
5
1 abc
3 abcabc
2 abc
1 aba
3 abababc
output
2
2
input
10
1 abc
1 bcd
1 abcd
3 abcd
2 abcd
3 abcd
2 bcd
3 abcd
2 abc
3 abcd

output

3
2
1
0

Solution

题目大意:给M个操作 1.插入一个串S  2.删除一个串S  3.给出一个串S,询问之前存在(插入后未被删除的)的串在S中出现的次数。

查询操作,显然是AC自动机随便做的,但是AC自动机不支持插入和删除。

不过可以考虑建多个AC自动机,查询时就全跑一边,这样显然是可以的,不过这样的复杂度明显是$O(N^{2})$的毫无意义。

但是建多个AC自动机是不可避免的了,不过即使这样时间复杂度完全可以从$O(N^{2})$优化下去,只需要优化建出的AC自动机数量就可以对总复杂度起到优化。

fail指针并不能快速合并,所以,只能暴力拆+暴力合,这种暴力拆解的合并,和启发式合并类似。

所以我们对这么多AC自动机按二进制分组,建出$logN$个AC自动机,这样时间复杂度就得到大幅优化。

这样实现起来其实也非常简单,用$root[i]$记录这个AC自动机的Trie树的根,顺带记录一下每个$root$的$size$,当当前的$root$的$size$和它前面的那个相同,就将他们合并,暴力拆小入大。

这样证明也和启发式合并一样,因为每个AC自动机最多被拆合$logN$次,所以复杂度是$O(NlogN)$

至于删除,比较麻烦的方法是在AC自动机上跑,然后把跑到的那个串的end标记-1。

但是,转化一下,我们所有删除的也按照上述方法处理建AC自动机,这样每次查询答案相减一下即可。
自己在写的时候有部分问题(常数优化)没及时注意到:
1.因为每次建AC自动机的时候,可能会进行合并,完全可以先进行合并再求一次fail指针,这样常数优化效果还是不错的。
2.在合并的时候,注意一下一边不存在时特判的写法,这里写的好坏,效率差距特别大!
优秀的写法:【Codeforces710F】String Set Queries     (强制在线)AC自动机 + 二进制分组

自己愚蠢的写法:【Codeforces710F】String Set Queries     (强制在线)AC自动机 + 二进制分组

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
#define MAXN 300010
int Q,opt,len,now;
char str[MAXN];
namespace ACMachine
{
struct Trie{int son[MAXN][],fail[MAXN],end[MAXN],tim[MAXN],sz;};
struct ACM
{
#define ch str[i]-'a'+1
int root[MAXN],cnt,size[MAXN];
Trie trie;
inline void Insert(int rt)
{
size[rt]++; now=rt;
for (int i=; i<=len; i++)
if (trie.son[now][ch]) now=trie.son[now][ch];
else trie.son[now][ch]=++trie.sz,now=trie.sz;
trie.end[now]=;
}
inline void Buildfail(int x)
{
queue<int>q;
q.push(x);
while (!q.empty())
{
now=q.front(); q.pop();
for (int i=; i<=; i++)
if (trie.son[now][i])
{
int fa=trie.fail[now];
while (fa && !trie.son[fa][i]) fa=trie.fail[fa];
trie.fail[trie.son[now][i]]=fa? trie.son[fa][i] : x;
trie.tim[trie.son[now][i]]=trie.end[trie.son[now][i]]+trie.tim[trie.fail[trie.son[now][i]]];
q.push(trie.son[now][i]);
}
}
}
inline int Merge(int ls,int rs)
{
if (!ls || !rs) return (ls+rs);
int rt=ls; if (!rt) return rt;
trie.end[rt]=trie.end[ls]|trie.end[rs];
for (int i=; i<=; i++)
trie.son[rt][i]=Merge(trie.son[ls][i],trie.son[rs][i]);
return rt;
}
inline int Debug()
{
int re=; now=root[cnt];
for (int i=; i<=len; i++)
{
while (now && !trie.son[now][ch]) now=trie.fail[now];
now=trie.son[now][ch];
re+=trie.tim[now];
}
printf("%d\n",re);
}
inline void MakeTrie()
{
root[++cnt]=++trie.sz;
Insert(root[cnt]);
while (cnt && size[cnt]==size[cnt-])
root[cnt-]=Merge(root[cnt-],root[cnt]),
size[cnt-]+=size[cnt],size[cnt]=,cnt--;
Buildfail(root[cnt]);
}
inline int Query()
{
int re=;
for (int r=; r<=cnt; r++)
for (int i=,now=root[r]; i<=len; i++)
{
while (now && !trie.son[now][ch]) now=trie.fail[now];
now=now? trie.son[now][ch] : root[r];
re+=trie.tim[now];
}
return re;
}
}In,Out;
}
using namespace ACMachine;
int main()
{
scanf("%d",&Q);
while (Q--)
{
scanf("%d%s",&opt,str+); len=strlen(str+);
switch (opt)
{
case : In.MakeTrie(); break;
case : Out.MakeTrie(); break;
case : printf("%d\n",In.Query()-Out.Query()); fflush(stdout); break;
}
}
return ;
}

一开始写丑了,无限卡常....CF迟迟case18TLE....

【Codeforces710F】String Set Queries (强制在线)AC自动机 + 二进制分组的更多相关文章

  1. CodeForces 710F 强制在线AC自动机

    题目链接:http://codeforces.com/contest/710/problem/F 题意:维护一个集合,集合要求满足三种操作. 1 str:向集合插入字符串str(保证不会插入之前已经插 ...

  2. CF710F-String Set Queries【AC自动机&comma;二进制分组】

    正题 题目链接:https://www.luogu.com.cn/problem/CF710F 题目大意 \(T\)次操作 往集合中加入一个字符串 往集合中删除一个字符串 给出一个模式串求出现的集合里 ...

  3. GRE Words Revenge AC自动机 二进制分组

    GRE Words Revenge 题意和思路都和上一篇差不多. 有一个区别就是需要移动字符串.关于这个字符串,可以用3次reverse来转换, 前面部分翻转一下, 后面部分翻转一下, 最后整个串翻转 ...

  4. CodeForces - 710F:String Set Queries (二进制分组 处理 在线AC自动机)

    ou should process m queries over a set D of strings. Each query is one of three kinds: Add a string ...

  5. hdu&lowbar;4787&lowbar;GRE Words Revenge&lpar;在线AC自动机&rpar;

    题目链接:hdu_4787_GRE Words Revenge 题意: 总共有n个操作,2种操作.每行读入一个字符串. 1.如果字符串以+开头,此为单词(即模式串,不考虑重复) 2.如果字符串以?开头 ...

  6. Codeforces963C Frequency of String 【字符串】【AC自动机】

    题目大意: 给一个串s和很多模式串,对每个模式串求s的一个最短的子串使得这个子串中包含至少k个该模式串. 题目分析: 均摊分析,有sqrt(n)种长度不同的模式串,所以有关的串只有msqrt(n)种. ...

  7. 题解-Codeforces710F String Set Queries

    咕了好久没更博客,最近得知可以去冬眠营玩耍,还可以搭顺风车回广州过年 (最近做到的比较有意思的题目:bzoj3958.hihocoder1419) Problem Codeforces-710F--洛 ...

  8. ZOJ3784 String of Infinity 高大上的AC自动机 数据原来这么水啊!不算输入输出只有5-7行

    找给定s集合里面word全部是同一个字符的,这样的word有几个,如果数量<m就yes,否则就no.#include<iostream> #include<cstring&gt ...

  9. AC自动机

    AC自动机,全称Aho-Corasick自动机.如果没记错的话好像就是前缀自动机. 其实AC自动机就是KMP上树的产物.理解了KMP,那AC自动机应该也是很好理解的. 与KMP类似,AC自动机也是扔一 ...

随机推荐

  1. Spring启动后扫描解析注解的过程

    对应的类: ComponentScanBeanDefinitionParser.parse() ClassPathBeanDefinitionScanner.doScan() 参考 http://bl ...

  2. 规则引擎集成接口(九)Java类对象

    Java类对象 右键点击“对象库” —“添加java类对象”,如下图: 弹出窗体,在文本框中输入类的全名“com.flagleader.test.Test”,选择该类型后确定,如下: 显示如下,勾选上 ...

  3. tableView的总结

    // // ViewController.m // TableViewController // // Created by 王妍 on 16/3/23. // Copyright © 2016年 c ...

  4. 渗透测试入门DVWA 教程1:环境搭建

    首先欢迎新萌入坑.哈哈.你可能抱着好奇心或者疑问.DVWA 是个啥? DVWA是一款渗透测试的演练系统,在圈子里是很出名的.如果你需要入门,并且找不到合适的靶机,那我就推荐你用DVWA. 我们通常将演 ...

  5. (1)Deep Learning之感知器

    What is deep learning? 在人工智能领域,有一个方法叫机器学习.在机器学习这个方法里,有一类算法叫神经网络.神经网络如下图所示: 上图中每个圆圈都是一个神经元,每条线表示神经元之间 ...

  6. app后端设计&lpar;14&rpar;--LBS的偏移问题

    刚开始做LBS的时候,有一个问题,通过手机获取的坐标,放到百度地图或高德地图上,总是会出现偏移,例如,当时是在微信的前总部"南方通讯大厦"附近获取的坐标,那是把坐标放到百度地图上却 ...

  7. BZOJ&lowbar;4378&lowbar;&lbrack;POI2015&rsqb;Logistyka&lowbar;树状数组

    BZOJ_4378_[POI2015]Logistyka_树状数组 Description 维护一个长度为n的序列,一开始都是0,支持以下两种操作: 1.U k a 将序列中第k个数修改为a. 2.Z ...

  8. 关于ORACLE的SQL语句拼接、替换、截取、排序,联表等&period;&period;&period;~持续汇总~

     先看一下所有的数据.这里全部为VARCHAR2(255). 字段拼接 在所有的性别后面加% 字段替换,把性别TPF_SEX去除百分号% 字段截取 字段截取+拼接 字段替换,这里把百分号%替换为空,也 ...

  9. java final static

    final: 修饰类:类不能被继承 修饰方法:方法不能被重写 修饰变量:不能修改变量的指向,且只能赋值一次 全局变量是有默认值的,所以如果用final修饰全局变量,能在定义的同时赋值,或在构造函数中赋 ...

  10. WPF实现Windows资源管理器&lpar;附源码&rpar;

      今天我来写一篇关于利用WPF来实现Windows的资源管理器功能,当然只是局部实现这个功能,因为在很多时候我们需要来实现对本机资源的管理,当然我们可以使用OpenFileDialog dialog ...