[NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]
题面
正文
最暴力的
最暴力的方法:把所有询问代表的字符串跑一遍kmp然后输出
稍微优化一下:把所有询问保存起来,把模板串相同的合并,求出next然后匹配
但是这两种方法本质没有区别,都是暴力
不那么暴力的
我们对于所有的串建立一个AC自动机,把询问按照$y$排序,然后在AC自动机上面跑,每次跳fail更新答案
这样可以拿到70分,但是时间上限还是会$O\left(n^2\right)$左右
巧妙的优化
这道题里面,所有的模板串和文本串都在AC自动机里
那么,题目中实际是在要求什么呢?
就是有多少个x串是y串的一个前缀的后缀
那么,在AC自动机自己身上有没有满足这样的检索的结构呢?
有的,那就是fail指针
trie上的某一个前缀的fail指针,指向的是作为它的最长后缀的那个节点;同时,从某个前缀开始一路沿着fail指针跳,直到根节点,过程中所有的节点代表的前缀都是这个前缀的后缀
也就是说,我们把fail指针看成树边,将这个“fail树”(不要和kmp的next树搞混了)提取出来,那么我们就可以把题目的询问变成这样:
把代表y串的所有前缀的节点打上标记,那么代表x串的节点的子树中的标记个数,就是这个询问的答案
维护个数和可以用fail树上的dfs序以及树状数组共同完成
正解
上述过程中有一个重复的地方:每次我们都需要把树状数组归零,然后重新把新的y串前缀节点插进去——即使我们使用把y排序的方法也会TLE
但是这个过程中有一个问题:有些点会进进出出好多遍,并不高效,我们需要找到一个办法,使得每个AC自动机上的点只进出树状数组一次
那么谁能满足这个要求呢?
还是dfs序,只不过是原trie树上的dfs序
我们把输入的询问按照y串在trie树上的dfs序排序,依次加入、删除
因为按照dfs序遍历可以使每个点进入一次离开一次,所以这个方法的总时间效率只有$O\left(nlogn\right)$
这样这道题就做完了
Code
本题的映射非常多,而且很繁复,有很多重复意义的东西,调试的时候一定要小心
变量名有点乱,还请见谅
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define rank deep_dark_fantasy
using namespace std;
struct node{
int fail,fa,son[26];
vector<int>num;
node(){fail=fa=0;memset(son,0,sizeof(son));num.clear();}
}a[100010];int cnt,tot;
int dfn[100010],clk,end[100010],tmplca,pre[100010],rank[100010];
//dfn是trie树dfs序,rank是dfn的反映射
//end是每个字符串在trie树上的节点编号
//pre表示由dfs序为i的串向dfs序为i+1的串转移时的lca,tmplca是维护这个的辅助变量
struct edge{
int to,next;
}e[100010];int cnte,first[100010];
inline void addedge(int u,int v){
e[++cnte]=(edge){v,first[u]};first[u]=cnte;
}
inline void add(char s[]){
int len=strlen(s),cur=0,i;
for(i=0;i<len;i++){
if(s[i]=='P'){a[cur].num.push_back(++tot);continue;}
if(s[i]=='B'){cur=a[cur].fa;continue;}
if(!a[cur].son[s[i]-'a']) a[cur].son[s[i]-'a']=++cnt;
a[a[cur].son[s[i]-'a']].fa=cur;cur=a[cur].son[s[i]-'a'];
}
}
void getdfn(int u){
int i,v,len=a[u].num.size();
for(i=0;i<len;i++){
dfn[++clk]=a[u].num[i];
rank[a[u].num[i]]=clk;
end[a[u].num[i]]=u;
pre[clk]=tmplca;tmplca=u;
}
for(i=0;i<26;i++){
v=a[u].son[i];if(!v) continue;
getdfn(v);tmplca=u;
}
}
int q[100010];
void getfail(){
int head=0,tail=0,i,u,v;
for(i=0;i<26;i++){
if(!a[0].son[i]) continue;
a[a[0].son[i]].fail=0;q[tail++]=a[0].son[i];
}
while(head<tail){
u=q[head++];
for(i=0;i<26;i++){
v=a[u].son[i];
if(v) a[v].fail=a[a[u].fail].son[i],q[tail++]=v;
else a[u].son[i]=a[a[u].fail].son[i];
}
}
memset(first,-1,sizeof(first));
for(i=1;i<=cnt;i++) addedge(a[i].fail,i);
}
char s[100010];int Q;
struct query{
int x,y,num,ans;
}qq[100010];
bool cmp(query l,query r){return rank[l.y]<rank[r.y];}
bool cmp2(query l,query r){return l.num<r.num;}
int now=0,tmpnow;
struct tree{//树状数组
int x[100010];
tree(){memset(x,0,sizeof(x));}
int lowbit(int pos){return pos&(-pos);}
void change(int pos,int type){
for(int i=pos;i<=cnt+1;i+=lowbit(i)) x[i]+=type;
}
int ask(int pos){
int re=0;
for(int i=pos;i>0;i-=lowbit(i)) re+=x[i];
return re;
}
}T;
int faildfn[100010],failclk=0,le[100010],ri[100010];
//faildfn是fail树上的dfs序,le和ri是某个节点在树状数组上的左右区间
void get_fail_dfn(int u){
int i,v;faildfn[u]=++failclk;le[u]=failclk;
for(i=first[u];~i;i=e[i].next){
v=e[i].to;
get_fail_dfn(v);
}
ri[u]=failclk;
}
int main(){
scanf("%s",s);int i,j,x,y,xx;
add(s);getdfn(0);
getfail();get_fail_dfn(0);
scanf("%d",&Q);
for(i=1;i<=Q;i++) scanf("%d%d",&qq[i].x,&qq[i].y),qq[i].num=i;
sort(qq+1,qq+Q+1,cmp);//排序
j=1;
for(i=1;i<=tot;i++){
y=dfn[i];tmpnow=end[y];
while(now!=pre[i]){
T.change(faildfn[now],-1);now=a[now].fa;
}
while(tmpnow!=now){
T.change(faildfn[tmpnow],1);tmpnow=a[tmpnow].fa;
}//插入、删除节点
now=end[y];
while(qq[j].y==y){//处理询问
xx=end[qq[j].x];
qq[j].ans=T.ask(ri[xx])-T.ask(le[xx]-1);
j++;
}
}
sort(qq+1,qq+Q+1,cmp2);
for(i=1;i<=Q;i++) printf("%d\n",qq[i].ans);
}
[NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]的更多相关文章
- BZOJ2434[Noi2011]阿狸的打字机——AC自动机+dfs序+树状数组
题目描述 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小 ...
- BZOJ 2434: [Noi2011]阿狸的打字机( AC自动机 + DFS序 + 树状数组 )
一个串a在b中出现, 那么a是b的某些前缀的后缀, 所以搞出AC自动机, 按fail反向建树, 然后查询(x, y)就是y的子树中有多少是x的前缀. 离线, 对AC自动机DFS一遍, 用dfs序+树状 ...
- 【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组
[BZOJ2434][NOI2011]阿狸的打字机 Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P ...
- NOI 2011 阿狸的打字机 (AC自动机+dfs序+树状数组)
题目大意:略(太长了不好描述) 良心LOJ传送门 先对所有被打印的字符串建一颗Trie树 观察数据范围,并不能每次打印都从头到尾暴力建树,而是每遍历到一个字符就在Trie上插入这个字符,然后记录每次打 ...
- BZOJ 2434 阿狸的打字机(ac自动机+dfs序+树状数组)
题意 给你一些串,还有一些询问 问你第x个串在第y个串中出现了多少次 思路 对这些串建ac自动机 根据fail树的性质:若x节点是trie中root到t任意一个节点的fail树的祖先,那么x一定是y的 ...
- BZOJ-3881:Divljak (AC自动机+DFS序+树链求并+树状数组)
Alice有n个字符串S_1,S_2...S_n,Bob有一个字符串集合T,一开始集合是空的. 接下来会发生q个操作,操作有两种形式: “1 P”,Bob往自己的集合里添加了一个字符串P. “2 x” ...
- CodeForces 547E:Mike and Friends(AC自动机+DFS序+主席树)
What-The-Fatherland is a strange country! All phone numbers there are strings consisting of lowercas ...
- CodeForces -163E :e-Government (AC自动机+DFS序+树状数组)
The best programmers of Embezzland compete to develop a part of the project called "e-Governmen ...
- BZOJ2434: [NOI2011]阿狸的打字机(AC自动机+dfs序+树状数组)
[NOI2011]阿狸的打字机 题目链接:https://www.luogu.org/problemnew/show/P2414 题目背景 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机. ...
随机推荐
- Drupal的入门学习
1. 注意content中的区别 Article和Basic page的区别 a.输入字段不一样,Article内容多了两个字段:tag和图片. b.内容的默认设置不一样,Article默认允许评论, ...
- java设计模式——单例模式(二)
破坏单例模式 上一章节,介绍了单例模式的几种方式,这次来学习一波我们创建的单例模式是否安全,能不能破坏.换句话说,也就是在程序运行中,不止有一个实例. 一. 序列化,反序列化破坏 以饿汉式的单例模式 ...
- css布局:块级元素的居中
一.定宽: 1.定位居中(absolute) 方法一: html: <div class="main"></main> css: .main{ width: ...
- 实用小工具不定期合集(textarea 高度自适应、自动计算Y轴刻度、json转table)
1.textarea高度自适应 这个非常有用,但是网上的解决方案都不尽人意,话不多说,上代码. function auto (elem) { var minHeight = 30 var change ...
- java 获取request中的请求参数
1.get 和 post请求方式 (1)request.getParameterNames(); 获取所有参数key后.遍历request.getParameter(key)获取value (2)re ...
- vim粘贴取消自动缩进
Vim 复制粘贴探秘 Vim 作为最好用的文本编辑器之一,使用vim来编文档,写代码实在是很惬意的事情.每当学会了vim的一个新功能,就会很大地提高工作效率.有人使用vim几 十年,还没有完全掌握vi ...
- JS:字符串转成json数据,和json转成字符串方法 iframe获取父级传过来的数据
字符串转成json数据,和json转成字符串方法 //转为JSON adinfo=JSON.parse(adinfo) //转为字符串 adinfo=JSON.stringify(adinfo) 大概 ...
- MySQL存储引擎MyISAM与InnoDB的区别比较
使用MySQL当然会接触到MySQL的存储引擎,在新建数据库和新建数据表的时候都会看到. MySQL默认的存储引擎是MyISAM,其他常用的就是InnoDB了. 至于到底用哪种存储引擎比较好?这个问题 ...
- 关于 composer 的一些坑
发布自己的『包.库』至 https://packagist.org 却一直不能引入 网络上所有关于新建composer包的教程文章统统只提到了版本可能会影响无法 require 深深的坑哭了我们这些入 ...
- Python入门必学:字符串和编码正确的使用方法
字符编码,我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特 ...