Codeforces 710F - String Set Queries(AC 自动机)
题意:强制在线的 AC 自动机。
\(n,\sum|s|\leq 3\times 10^5\)
如果不是强制在线那此题就是道 sb 题,加了强制在线就不那么 sb 了。
这里介绍两种做法:
- 根号分治
考虑到 KMP 擅长处理单个字符串匹配的情况,但对于多模式串的情况复杂度就不那么优秀了。
而 AC 自动机擅长处理多模式串匹配的情况,但预处理复杂度是线性的,每加进来一个字符串都预处理一遍复杂度显然吃不消。
考虑将二者结合,设一个临界值 \(B\),每 \(B\) 个串建一个 AC 自动机。剩余的若干个串暴力跑 KMP。
算下复杂度,最多要在 AC 自动机上匹配 \(\frac{n}{B}\) 次,每次匹配都是 \(|s|\) 的,故 AC 自动机部分的复杂度是 \(\frac{n}{B}\times \sum|s|\)。
再考虑 KMP,单次 KMP 是 \(\mathcal O(n+m)\) 的,每个模式串最多跟 \(B\) 个询问串匹配,而每个询问串也最多跟 \(B\) 个模式串匹配,所以贡献为 \(B\sum|s|\)。
最后是删除操作,众所周知,AC 自动机是不支持删除操作的,但发现一加一减,贡献可以抵消掉,所有我们可以建两个 AC 自动机,一个处理所有 \(1\) 操作加入进来的字符串,一个处理所有 \(2\) 操作删除的字符串,二者相减即可。
总复杂度 \(\mathcal O(\sum|s|(B+\frac{n}{B}))\),\(3\times 10^5\) \(n\sqrt{n}\) 大约是 \(2\times 10^8\) 级别,理论上是可以的,可惜我没卡过去 qwq。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
char c=getchar();T neg=1;
while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
x*=neg;
}
const int BLK=548;
const int MAXN=3e5+5;
const int ALPHA=26;
char buf[MAXN+5];
string reads(){
scanf("%s",buf+1);int len=strlen(buf+1);
string ret;for(int i=1;i<=len;i++) ret+=buf[i];
return ret;
}
class solver{
public:
struct ACAM{
int rt[BLK+5];
int ch[MAXN+BLK+5][ALPHA+2],fail[MAXN+BLK+5],cnt[MAXN+BLK+5],ncnt=0;
void insert(int r,string s){
int cur=r;
for(int i=0;i<s.size();i++){
if(!ch[cur][s[i]-'a']) ch[cur][s[i]-'a']=++ncnt;
cur=ch[cur][s[i]-'a'];
} cnt[cur]++;
}
void getfail(int r){
queue<int> q;
for(int i=0;i<ALPHA;i++){
if(ch[r][i]) q.push(ch[r][i]),fail[ch[r][i]]=r;
else ch[r][i]=r;
}
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<ALPHA;i++){
if(ch[x][i]) fail[ch[x][i]]=ch[fail[x]][i],q.push(ch[x][i]),cnt[ch[x][i]]+=cnt[fail[ch[x][i]]];
else ch[x][i]=ch[fail[x]][i];
}
}
}
int query(int r,string s){
int cur=r,ret=0;
for(int i=0;i<s.size();i++){
cur=ch[cur][s[i]-'a'];ret+=cnt[cur];
} return ret;
}
} a;
int fail[MAXN+5];
int getkmp(string s,string t){
int ls=s.size(),lt=t.size();s=" "+s;t=" "+t;
int pos=0,ret=0;
for(int i=2;i<=lt;i++){
while(pos&&t[pos+1]!=t[i]) pos=fail[pos];
if(t[pos+1]==t[i]) pos++;fail[i]=pos;
} pos=0;
for(int i=1;i<=ls;i++){
while(pos&&t[pos+1]!=s[i]) pos=fail[pos];
if(t[pos+1]==s[i]) pos++;
if(pos==lt) ret++;
} return ret;
}
string ss[MAXN+5];
int num=0,pre=0,cnt=0;
void insert(string s){
ss[++num]=s;
if(num%BLK==0){
a.rt[++cnt]=++a.ncnt;
for(int i=pre+1;i<=num;i++) a.insert(a.rt[cnt],ss[i]);
a.getfail(a.rt[cnt]);pre=num;
}
}
int query(string s){
int ret=0;
for(int i=1;i<=cnt;i++) ret+=a.query(a.rt[i],s);
for(int i=pre+1;i<=num;i++) ret+=getkmp(s,ss[i]);
return ret;
}
} s1,s2;
int T;
int main(){
scanf("%d",&T);
while(T--){
int opt;string s;scanf("%d",&opt);s=reads();
if(opt==1) s1.insert(s);
else if(opt==2) s2.insert(s);
else printf("%d\n",s1.query(s)-s2.query(s)),fflush(stdout);
}
return 0;
}
- 二进制分组
感觉这应该是正解吧,本题官方题解给的就这个做法。
还是将加入的字符串分成若干组,每组建一个 AC 自动机。
不过与之前不同的是这次我们按二进制分组,即每组的大小都是 \(2\) 的整数次幂。
那么这玩意儿怎么支持插入操作呢?假设我们插入字符串 \(s\),我们先建一个只有一个串的 AC 自动机,然后不断与前面的 AC 自动机像启发式合并堆一样合并。最后暴力重构一发。
是不是有点抽象?打个形象的比喻,2048,假设我们现在有 \(23\) 个串,那么会分为 \(4\) 组,大小分别为 \(16,4,2,1\),此时你再加入一个串,就变为 \(16,4,2,1,1\),最后两个 \(1\) 合并,变为一个 \(2\);最后两个 \(2\) 合并,变为一个 \(4\)……以此类推。最后会得到 \(16,8\)。
算下复杂度,暴力重构复杂度是 \(\sum|s|\) 的,而我们每个字符串最多被重构 \(\log n\) 次,故插入的总复杂度是 \(n\log n\) 的,而查询的时候你最多在 \(\log n\) 个 AC 自动机中查询,故总复杂度为 \(n\log n\),碾压算法 1。
实测 1s,可能因为有个 \(26\) 的常数吧。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
char c=getchar();T neg=1;
while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
x*=neg;
}
const int MAXN=3e5;
const int LOG_N=19;
const int ALPHA=26;
class solver{
public:
int sz[LOG_N+3],rt[LOG_N+3],cnt=0;
int oc[MAXN*2+5][ALPHA+2],ch[MAXN*2+5][ALPHA+2],ncnt=0,ed[MAXN*2+5],val[MAXN*2+5],fail[MAXN+5];
void insert(char *s,int r){
int len=strlen(s+1),cur=r;
for(int i=1;i<=len;i++){
if(!oc[cur][s[i]-'a']) oc[cur][s[i]-'a']=++ncnt;
cur=oc[cur][s[i]-'a'];
} ed[cur]++;
}
void getfail(int r){
queue<int> q;
for(int i=0;i<ALPHA;i++){
if(oc[r][i]){
fail[oc[r][i]]=r;q.push(oc[r][i]);
val[oc[r][i]]=ed[oc[r][i]];ch[r][i]=oc[r][i];
} else ch[r][i]=r;
}
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<ALPHA;i++){
if(oc[x][i]){
ch[x][i]=oc[x][i];
fail[oc[x][i]]=ch[fail[x]][i];
val[oc[x][i]]=val[fail[oc[x][i]]]+ed[oc[x][i]];
q.push(oc[x][i]);
} else ch[x][i]=ch[fail[x]][i];
}
}
}
int merge(int x,int y){
if(!x||!y) return x+y;
ed[x]+=ed[y];
for(int i=0;i<ALPHA;i++) oc[x][i]=merge(oc[x][i],oc[y][i]);
return x;
}
void insert(char *s){
sz[++cnt]=1;rt[cnt]=++ncnt;insert(s,rt[cnt]);
while(sz[cnt]==sz[cnt-1]){
rt[cnt-1]=merge(rt[cnt-1],rt[cnt]);
sz[cnt-1]<<=1;sz[cnt]=0;cnt--;
} getfail(rt[cnt]);
}
int query(char *s,int r){
int len=strlen(s+1),cur=r,ret=0;
for(int i=1;i<=len;i++){
cur=ch[cur][s[i]-'a'];
ret+=val[cur];
} return ret;
}
int query(char *s){
int ret=0;
for(int i=1;i<=cnt;i++) ret+=query(s,rt[i]);
return ret;
}
} s1,s2;
char buf[MAXN+5];
int main(){
int T;scanf("%d",&T);
while(T--){
int opt;scanf("%d%s",&opt,buf+1);
if(opt==1) s1.insert(buf);
else if(opt==2) s2.insert(buf);
else printf("%d\n",s1.query(buf)-s2.query(buf)),fflush(stdout);
}
return 0;
}
Codeforces 710F - String Set Queries(AC 自动机)的更多相关文章
- Codeforces 710F String Set Quries
题意 维护一个字符串的集合\(D\), 支持3种操作: 插入一个字符串\(s\) 删除一个字符串\(s\) 查询一个字符串\(s\)在\(D\)中作为子串出现的次数 强制在线 解法 AC自动机+二进制 ...
- Codeforces 291 E Tree-String Problem AC自动机
Tree-String Problem 网上的dfs + kmp 复杂度就是错的, 除非算出根据下一个字符直接转移Next数组直接转移, 而求出Next[ i ][ 26 ]数组和丢进AC自动机里面没 ...
- CodeForces - 697F:Legen... (AC自动机+矩阵)
Barney was hanging out with Nora for a while and now he thinks he may have feelings for her. Barney ...
- CodeForces -163E :e-Government (AC自动机+DFS序+树状数组)
The best programmers of Embezzland compete to develop a part of the project called "e-Governmen ...
- Codeforces 1163D Mysterious Code(AC自动机+DP)
用 AC自动机 来做有点想不到,捞一手就是学一手. 设 dp[ i ][ j ] 表示字符串 c 中的第 i 位到字典树上节点 j 的最大值是多少, word[ j ] 表示在节点 j 下对答案修改的 ...
- Searching the String ZOJ - 3228 AC自动机查询升级版
题意:先给你一个不超过1000000长度的大串s:接下来输入一个n代表接下来输入的小串个数,小串长度不超过6. 小串分两种类型0和1类型. 0类型表示小串在大串中的最大匹配个数就是常规的AC自动机的做 ...
- Codeforces 86C Genetic engineering(AC自动机+DP)
题目大概是给几个DNA片段,求构造一个长度n的字符串的方案数,要求这个字符串每个位置的字符都属于某个包含于此字符串的DNA片段. 把那些DNA片段建一个AC自动机.考虑状态的表示: dp[len][x ...
- CodeForces 547E Mike and Friends AC自动机 主席树
题意: 给出\(n\)个字符串\(s_i\)和\(q\)个询问: \(l,r,k\):\(\sum\limits_{i=l}^{r}count(i, k)\),其中\(count(i,j)\)表示\( ...
- ZOJ3784 String of Infinity(AC自动机&&强连通分量)
题意:给你n个禁止串,然后你只能用字符表的前m个字符去写一个无限长的串,要求是不能包含禁止串,而且串在后面不能出现循环 比赛的时候想的是先建一个自动机,然后将自动机确定化,不能到达的状态全部弄出来.但 ...
随机推荐
- 小白自制Linux开发板 六. SPI TFT屏幕修改与移植
本文章参考:https://www.bilibili.com/read/cv9947785?spm_id_from=333.999.0.0 本篇通过SPI接口,使用ST7789V TFT焊接屏(13p ...
- 【UE4 C++】定时器 Timer 与事件绑定
概念 定时执行操作,可执行一次,或循环执行直到手动终止 定时器在全局定时器管理器(FTimerManager 类型)中管理.全局定时器管理器存在于 游戏实例 对象上以及每个 场景 中 定时器需要绑定委 ...
- Scrum Meeting 0429
零.说明 日期:2021-4-29 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 qsy PM&前端 完成部分后端管理 ...
- 浅谈如何爆踩TLEcoders
对付一些速度比老奶奶都慢的评测姬, 除了超级小的常数,往往还不得不使用一些不算办法的办法 比如说这个让人无语的$ACcoders$的评测姬, 当我们感到代码已经无法再卡常的时候,对人生已经近乎绝望的时 ...
- C语言中都有哪些常见的数据结构你都知道几个??
上次在面试时被面试官问到学了哪些数据结构,那时简单答了栈.队列/(ㄒoㄒ)/~~其它就都想不起来了,今天有空整理了一下几种常见的数据结构,原来我们学过的数据结构有这么多~ 首先,先来回顾下C语言中常见 ...
- STM32 PWM功能在关闭时GPIO电平不确定的情况
刚开始接触STM32,遇到一个项目中出现在产品调试中出现在关闭PWM输出时,GPIO电平有不确定的情况.在网上查阅资料发现大神们是这样解释的:PWM在一个脉冲没有结束时关闭输出,会导致GPIO电平不确 ...
- Shell脚本学习笔记之(自动填充函数模板)
其实,vii 就是写的一个脚本,跟 vi 没半毛钱关系,只不过借用一下这个名字而已.那这个脚本长什么样呢?look: 下面来详细的解析上面的代码,来看第1行: #!/bin/bash 这是Shell脚 ...
- Java I/O框架 - 总结概述
总结 以下需要重点掌握: 字节流,以下读取结束全部返回-1 字节节点流-访问文件 FileInputStream/FileOutputStream 可以读取任意文件 可以复制图片 读取字符String ...
- GPS与AGPS定位服务
最近客户反馈车子启动从车库开到地面后,机器定位相对OBD内部定位会慢很多. 机器定位主要依赖定位模块 + AGPS辅助定位. 其中定位模块目前主流支持的有以下三种定位系统. 一.GPS(全球定位系统) ...
- hdu 2795 Billboard(单点更新,区间查询)
题意: h*w的白板. 有n个广告牌,每个广告牌是1*wi.必须放置在白板的upmost中的leftmost. 输出n个广告牌放置在第几行.如果放不下,输出-1. 数据规格: h, w, and n ...