Codeforces 587F - Duff is Mad(根号分治+AC 自动机+树状数组)
第一眼看成了 CF547E……
话说 CF587F 和 CF547E 出题人一样欸……还有另一道 AC 自动机的题 CF696D 也是这位名叫 PrinceOfPersia 的出题人出的……似乎这位神仙很擅长字符串?
跑题了跑题了……
令 \(m=\sum|s_i|\)
u1s1 我做这题的时候一直在考虑按照 CF547E 的套路对询问进行差分处理,然鹅并没有什么卵用。因为这题相当于每次加入一个字符串时将这个字符串结尾位置 fail 树的子树中的所有节点访问次数 \(+1\),并询问某个节点 \(x\) 在 trie 树上到根节点路径上所有点总共被标记了多少次,然鹅这个东西是无法用 DS 直接维护的,trie 树与 fail 树之间也无法通过 DFS 序什么的建立直接的联系,故该算法是没有前途的。
考虑换一种思路。首先我们可以想到一个最最暴力的做法,将 trie 树上 \(s_k\) 结尾位置到根节点的路径上所有点访问次数 \(+1\),并枚举所有字符串 \(i\in [l,r]\),统计 \(s_i\) 在 fail 树的子树中所有点的标记次数之和(这也就是 \(s_i\) 在 \(s_k\) 中出现的次数)并累加进答案中。其次我们还可以想到一个刚才那个算法的暴力版本,还是将询问差分处理,拆成两个形如“前 \(x\) 个字符串在 \(s_y\) 中总共出现了多少次”并将这样的询问挂在 \(x\) 上。考虑动态插入这些字符串,当我们插入某个字符串 \(s_i\) 的时候就用树状数组+DFS 序令 \(s_i\) 在 fail 树的子树中所有点的标记次数 \(+1\),然而对于查询就没有什么好方法了,只能暴力跳 \(s_k\) 在 trie 树上的祖先并将该路径上所有点的标记次数累加入答案。
还是考虑将两个暴力结合一下,不难发现第一个暴力对每个询问都暴力求了一遍,在 \(q\) 比较小的时候能体现出较大优势,第二个暴力插入某个字符串时操作效率较高,但查询出现次数时复杂度与 \(|s_k|\) 的长度有关,在 \(|s_i|\) 比较小的时候能体现出较大优势。按照套路根分,设一个阈值 \(T\),对于长度 \(>T\) 的字符串 \(s_k\) 采用暴力一求出所有字符串在 \(s_k\) 中出现的次数,查询的时候直接前缀和相减即可,由于这样的字符串最多 \(\dfrac{m}{T}\) 个,故复杂度 \(\dfrac{m^2}{T}\)。对于长度 \(\leq T\) 的字符串就采用暴力二,复杂度 \(qT\log m\),总复杂度 \(\dfrac{m^2}{T}+qT\log m\),根据均值不等式可知 \(T=\dfrac{m}{\sqrt{q\log m}}\) 时复杂度最优。
当然如果使用区间修改 \(\mathcal O(\sqrt{m})\),单点查询 \(\mathcal O(1)\) 的分块可使暴力二部分的复杂度降到 \(n\sqrt{m}+qT\),但由于直接树状数组能过就没写了。
#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){
x=0;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=1e5;
const int ALPHA=26;
int n,qu,m=0,blk;string s[MAXN+5];
int ch[MAXN+5][ALPHA+2],ncnt=0,fail[MAXN+5],ed[MAXN+5];
void insert(string s,int id){
int cur=0;
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'];
} ed[id]=cur;
}
void getfail(){
queue<int> q;
for(int i=0;i<ALPHA;i++) if(ch[0][i]) q.push(ch[0][i]);
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]);}
else ch[x][i]=ch[fail[x]][i];
}
}
}
int hd[MAXN+5],to[MAXN+5],nxt[MAXN+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int tim=0,bgt[MAXN+5],edt[MAXN+5];
void dfs(int x){bgt[x]=++tim;for(int e=hd[x];e;e=nxt[e]) dfs(to[e]);edt[x]=tim;}
ll sum[MAXN+5],ss[MAXN+5];
struct query{int l,r,k;} q[MAXN+5];
vector<int> large;
vector<pii> qv[MAXN+5];
ll ans[MAXN+5];
ll t[MAXN+5];
void add(int x,int v){for(int i=x;i<=ncnt+1;i+=(i&(-i))) t[i]+=v;}
ll query(int x){int ret=0;for(int i=x;i;i&=(i-1)) ret+=t[i];return ret;}
int main(){
scanf("%d%d",&n,&qu);
for(int i=1;i<=n;i++) cin>>s[i],m+=s[i].size(),insert(s[i],i);
blk=(int)m/sqrt(qu*log(m)/log(2));
getfail();for(int i=1;i<=ncnt;i++) adde(fail[i],i);dfs(0);
for(int i=1;i<=qu;i++){
scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].k);
if(s[q[i].k].size()>blk) large.pb(i);
else qv[q[i].l-1].pb(mp(i,-1)),qv[q[i].r].pb(mp(i,1));
}
sort(large.begin(),large.end(),[](int x,int y){return q[x].k<q[y].k;});
for(int i=0;i<large.size();i++){
if(i==0||q[large[i]].k!=q[large[i-1]].k){
int id=q[large[i]].k,cur=0;
memset(sum,0,sizeof(sum));
memset(ss,0,sizeof(ss));
for(int j=0;j<s[id].size();j++) cur=ch[cur][s[id][j]-'a'],sum[bgt[cur]]++;
for(int j=1;j<=ncnt+1;j++) sum[j]+=sum[j-1];
for(int j=1;j<=n;j++) ss[j]=ss[j-1]+sum[edt[ed[j]]]-sum[bgt[ed[j]]-1];
} ans[large[i]]=ss[q[large[i]].r]-ss[q[large[i]].l-1];
}
for(int i=1;i<=n;i++){
add(bgt[ed[i]],1);add(edt[ed[i]]+1,-1);
for(int j=0;j<qv[i].size();j++){
int id=qv[i][j].fi,mul=qv[i][j].se,cur=0;
ll ret=0;
for(int k=0;k<s[q[id].k].size();k++){
cur=ch[cur][s[q[id].k][k]-'a'];
ret+=query(bgt[cur]);
} ans[id]+=ret*mul;
}
}
for(int i=1;i<=qu;i++) printf("%lld\n",ans[i]);
return 0;
}
Codeforces 587F - Duff is Mad(根号分治+AC 自动机+树状数组)的更多相关文章
- 洛谷P2414 阿狸的打字机 [NOI2011] AC自动机+树状数组/线段树
正解:AC自动机+树状数组/线段树 解题报告: 传送门! 这道题,首先想到暴力思路还是不难的,首先看到y有那么多个,菜鸡如我还不怎么会可持久化之类的,那就直接排个序什么的然后按顺序做就好,这样听说有7 ...
- 【BZOJ】2434: [Noi2011]阿狸的打字机 AC自动机+树状数组+DFS序
[题意]阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小写 ...
- CF587F Duff is Mad(AC自动机+树状数组+分块)
考虑两一个暴力 1 因为询问\([a,b]\)可以拆成\([1,b]\)-\([1,a-1]\)所以把询问离线,然后就是求\([1,x]\)中被\(S_i\)包含的串的数量.考虑当\([1,x-1]- ...
- Codeforces 547E - Mike and Friends(AC 自动机+树状数组)
题面传送门 好久每做过 AC 自动机的题了--做几个题回忆一下罢 AC 自动机能够解决多串匹配问题,注意是匹配,碰到前后缀的问题那多半不在 AC 自动机能解决的范围内. 在初学 AC 自动机的时候相信 ...
- BZOJ2434: [Noi2011]阿狸的打字机(AC自动机 树状数组)
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 4140 Solved: 2276[Submit][Status][Discuss] Descript ...
- BZOJ3881[Coci2015]Divljak——AC自动机+树状数组+LCA+dfs序+树链的并
题目描述 Alice有n个字符串S_1,S_2...S_n,Bob有一个字符串集合T,一开始集合是空的. 接下来会发生q个操作,操作有两种形式: “1 P”,Bob往自己的集合里添加了一个字符串P. ...
- HDU 6096 String(AC自动机+树状数组)
题意 给定 \(n\) 个单词,\(q\) 个询问,每个询问包含两个串 \(s_1,s_2\),询问有多少个单词以 \(s_1\) 为前缀, \(s_2\) 为后缀,前后缀不能重叠. \(1 \leq ...
- bzoj 2434 AC自动机+树状数组
2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 3493 Solved: 1909[Submit][Sta ...
- [NOI2011]阿狸的打字机 --- AC自动机 + 树状数组
[NOI2011] 阿狸的打字机 题目描述: 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机. 打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现, ...
随机推荐
- java---String 和 StringBuffer
Java-String和StringBuffer类 Java String 类 字符串在Java中属于对象,Java提供String类来创建和操作字符串. 创建字符串 创建字符串常用的方法如下: ...
- Golang通脉之面向对象
面向对象的三大特征: 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式 继承:使得子类具有父类的属性和方法或者重新定义.追加属性和方法等 多态:不同对象中同种行为的不同实现方式 Go并不是一个纯 ...
- 【数据结构与算法Python版学习笔记】树——相关术语、定义、实现方法
概念 一种基本的"非线性"数据结构--树 根 枝 叶 广泛应用于计算机科学的多个领域 操作系统 图形学 数据库 计算机网络 特征 第一个属性是层次性,即树是按层级构建的,越笼统就越 ...
- Java编程开发学习路线图(附所有免费课程+在线自测)
转自 https://yq.aliyun.com/articles/134286?spm=5176.100239.0.0.1UfveS 摘要: 长期以来,Java一直占据TIOBE编程语言排行版第一 ...
- BUAA2020软工团队beta得分总表
BUAA2020软工团队beta得分总表 [TOC] 零.团队博客目录及beta阶段各部分博客地址 团队博客 计划与设计博客 测试报告博客 发布声明博客 事后分析博客 敏 杰 开 发♂ https:/ ...
- Vue3+Typescript+Node.js实现微信端公众号H5支付(JSAPI v3)教程--各种填坑
----微信支付文档,不得不说,挺乱!(吐槽截止) 功能背景 微信公众号中,点击菜单或者扫码,打开公众号中的H5页面,进行支付. 一.技术栈 前端:Vue:3.0.0,typescript:3.9.3 ...
- Spark面试题(二)
首发于我的个人博客:Spark面试题(二) 1.Spark有哪两种算子? Transformation(转化)算子和Action(执行)算子. 2.Spark有哪些聚合类的算子,我们应该尽量避免什么类 ...
- UVM:6.2.3 sequencer 的grab 操作
转载:UVM:6.2.3 sequencer 的grab 操作_tingtang13的博客-CSDN博客 1.grab 比lock 优先级更高. 2.lock 是插到sequencer 仲裁队列的后面 ...
- 六. Go并发编程--WaitGroup
一. 序言 WaitGroup是Golang应用开发过程中经常使用的并发控制技术. WaitGroup,可理解为Wait-Goroutine-Group,即等待一组goroutine结束.比如某个go ...
- [WPF] 使用三种方式实现弧形进度条
1. 需求 前天看到有人问弧形进度条怎么做,我模仿了一下,成果如下图所示: 当时我第一反应是可以用 Microsoft.Toolkit.Uwp.UI.Controls 里的 RadialGauge 实 ...