【BZOJ2434】阿狸的打字机(AC自动机,树状数组)
【BZOJ2434】阿狸的打字机(AC自动机,树状数组)
先写个暴力:
每次打印出字符串后,就插入到\(Trie\)树中
搞完后直接搭\(AC\)自动机
看一看匹配是怎么样的:
每次沿着\(AC\)自动机走,在每一个节点都跳\(fail\)指针
如果有\(x\)串的末节点,就给答案\(+1\)
这样的话没有必要存下每个串
只要给\(AC\)自动机存一个父亲节点
记录一下每个串的结束位置
倒着往上跳就可以了
这样能够拿到\(40\)分
Update2018.1.25:这份代码对于重复串的处理会有问题,感谢 @超级大蒟蒻
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define MAX 200000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
char ss[MAX];
int nd[MAX],n,tot;
struct Node
{
int vis[26];
int fail,fa;
int lt;
}t[MAX];
void GetFail()
{
queue<int> Q;
for(int i=0;i<26;++i)
if(t[0].vis[i])Q.push(t[0].vis[i]);
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=0;i<26;++i)
if(t[u].vis[i])
t[t[u].vis[i]].fail=t[t[u].fail].vis[i],Q.push(t[u].vis[i]);
else t[u].vis[i]=t[t[u].fail].vis[i];
}
}
int Query(int x,int y)
{
int ret=0;
int now=nd[y];
while(now)
{
for(int i=now;i;i=t[i].fail)
if(t[i].lt==x){++ret;break;}
now=t[now].fa;
}
return ret;
}
int main()
{
scanf("%s",ss+1);
int now=0;
for(int i=1,l=strlen(ss+1);i<=l;++i)
{
if(ss[i]>='a'&&ss[i]<='z')
{
if(!t[now].vis[ss[i]-'a'])t[now].vis[ss[i]-'a']=++tot,t[tot].fa=now;
now=t[now].vis[ss[i]-'a'];
}
if(ss[i]=='B')now=t[now].fa;
if(ss[i]=='P'){nd[++n]=now;t[now].lt=n;}
}
int Q=read();
GetFail();
while(Q--)
{
int x=read(),y=read();
printf("%d\n",Query(x,y));
}
return 0;
}
这样子对于每一个询问都会要暴跳
如果对于某个串有重复的多次询问
那么就会多很多次没有任何意义的计算
所以,可以离线把所有询问都按照\(y\)排序
每次跳的时候开个桶一起计算
这样的话可以拿到\(70\)分
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define MAX 200000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
char ss[MAX];
int nd[MAX],n,tot;
int ans[MAX];
struct Node
{
int vis[26];
int fail,fa;
int lt;
}t[MAX];
struct Question{int x,y,id,ans;}q[MAX];
bool operator<(Question a,Question b){return a.y<b.y;}
int sum[MAX];
void GetFail()
{
queue<int> Q;
for(int i=0;i<26;++i)
if(t[0].vis[i])Q.push(t[0].vis[i]);
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=0;i<26;++i)
if(t[u].vis[i])
t[t[u].vis[i]].fail=t[t[u].fail].vis[i],Q.push(t[u].vis[i]);
else t[u].vis[i]=t[t[u].fail].vis[i];
}
}
int Query(int y)
{
int ret=0;
int now=nd[y];
while(now)
{
for(int i=now;i;i=t[i].fail)
if(t[i].lt)sum[t[i].lt]++;
now=t[now].fa;
}
return ret;
}
int main()
{
scanf("%s",ss+1);
int now=0;
for(int i=1,l=strlen(ss+1);i<=l;++i)
{
if(ss[i]>='a'&&ss[i]<='z')
{
if(!t[now].vis[ss[i]-'a'])t[now].vis[ss[i]-'a']=++tot,t[tot].fa=now;
now=t[now].vis[ss[i]-'a'];
}
if(ss[i]=='B')now=t[now].fa;
if(ss[i]=='P'){nd[++n]=now;t[now].lt=n;}
}
int Q=read();
GetFail();
for(int i=1;i<=Q;++i)
{
q[i].x=read(),q[i].y=read();
q[i].id=i;
}
sort(&q[1],&q[Q+1]);
for(int i=1,pos=1;i<=Q;i=pos)
{
Query(q[i].y);
while(q[pos].y==q[i].y)q[pos].ans=sum[q[pos].x],pos++;
memset(sum,0,sizeof(sum));
}
for(int i=1;i<=Q;++i)ans[q[i].id]=q[i].ans;
for(int i=1;i<=Q;++i)
printf("%d\n",ans[i]);
return 0;
}
再来想想我们每次在干什么??
跳\(fail\)
显然每个节点有且仅有一个\(fail\)指针
所以,这就是一棵树??
把这个\(fail\)反过来看
现在的问题是什么?
原来是\(y\)的某个节点往上跳能不能到达\(x\)
现在反过来:
\(x\)往下跳能够到达几个\(y\)的节点
那,不就是求子树和???
如果把所有\(y\)的节点全部打上一个\(1\)的标记
那么,每次就变成了求\(x\)末节点的子树和
而一个点的子树在\(dfs\)序上一定是连续的一段
这样还是可以拿到\(70\)分
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define MAX 200000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
char ss[MAX];
int nd[MAX],n,tot;
int ans[MAX];
int c[MAX];
int dfn[MAX],low[MAX],tim;
inline int lowbit(int x){return x&(-x);}
void Modify(int x,int w){while(x<=tim)c[x]+=w,x+=lowbit(x);}
int getsum(int x){int ret=0;while(x)ret+=c[x],x-=lowbit(x);return ret;}
struct Node
{
int vis[26];
int fail,fa;
int lt;
}t[MAX];
struct Question{int x,y,id,ans;}q[MAX];
bool operator<(Question a,Question b){return a.y<b.y;}
void GetFail()
{
queue<int> Q;
for(int i=0;i<26;++i)
if(t[0].vis[i])Q.push(t[0].vis[i]);
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=0;i<26;++i)
if(t[u].vis[i])
t[t[u].vis[i]].fail=t[t[u].fail].vis[i],Q.push(t[u].vis[i]);
else t[u].vis[i]=t[t[u].fail].vis[i];
}
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
void dfs(int u)
{
dfn[u]=++tim;
for(int i=h[u];i;i=e[i].next)dfs(e[i].v);
low[u]=tim;
}
int main()
{
scanf("%s",ss+1);
int now=0;
for(int i=1,l=strlen(ss+1);i<=l;++i)
{
if(ss[i]>='a'&&ss[i]<='z')
{
if(!t[now].vis[ss[i]-'a'])t[now].vis[ss[i]-'a']=++tot,t[tot].fa=now;
now=t[now].vis[ss[i]-'a'];
}
if(ss[i]=='B')now=t[now].fa;
if(ss[i]=='P'){nd[++n]=now;t[now].lt=n;}
}
int Q=read();
GetFail();
for(int i=1;i<=tot;++i)Add(t[i].fail,i);
dfs(0);
for(int i=1;i<=Q;++i)
{
q[i].x=read(),q[i].y=read();
q[i].id=i;
}
sort(&q[1],&q[Q+1]);
for(int i=1,pos=1;i<=Q;i=pos)
{
for(int now=nd[q[i].y];now;now=t[now].fa)
Modify(dfn[now],1);
while(q[pos].y==q[i].y)
{
int v=nd[q[pos].x];
q[pos].ans=getsum(low[v])-getsum(dfn[v]-1);
pos++;
}
memset(c,0,sizeof(c));
}
for(int i=1;i<=Q;++i)ans[q[i].id]=q[i].ans;
for(int i=1;i<=Q;++i)
printf("%d\n",ans[i]);
return 0;
}
现在大致的方向已经没有问题了
看看我们重复算在哪里?
每次把串插入进树状数组!
因为很多的串会有重复
所以会反反复复把很多东西给重复插进去
这样就很慢了
于是,我们把\(Trie\)树\(dfs\)遍历一遍
我搞Fail指针的时候会把原来的Trie数给搞掉,还要备份。。
访问到的时候打一个\(+1\)
结束的时候打一个\(-1\)
每次访问到一个结束节点的时候,
一定是有且仅有这个串的节点被打了标记
这样就可以直接回答这个串的相关询问了
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define MAX 200000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
char ss[MAX];
int nd[MAX],n,tot;
int ans[MAX];
int c[MAX];
int dfn[MAX],low[MAX],tim;
int ql[MAX],qr[MAX];
inline int lowbit(int x){return x&(-x);}
void Modify(int x,int w){while(x<=tim)c[x]+=w,x+=lowbit(x);}
int getsum(int x){int ret=0;while(x)ret+=c[x],x-=lowbit(x);return ret;}
struct Node
{
int vis[26];
int Vis[26];
int fail,fa;
int lt;
}t[MAX];
struct Question{int x,y,id,ans;}q[MAX];
bool operator<(Question a,Question b){return a.y<b.y;}
void GetFail()
{
queue<int> Q;
for(int i=0;i<26;++i)
if(t[0].vis[i])Q.push(t[0].vis[i]);
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=0;i<26;++i)
if(t[u].vis[i])
t[t[u].vis[i]].fail=t[t[u].fail].vis[i],Q.push(t[u].vis[i]);
else t[u].vis[i]=t[t[u].fail].vis[i];
}
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
void dfs(int u)
{
dfn[u]=++tim;
for(int i=h[u];i;i=e[i].next)dfs(e[i].v);
low[u]=tim;
}
void DFS(int u)
{
Modify(dfn[u],1);
if(t[u].lt)
for(int i=ql[t[u].lt];i<=qr[t[u].lt];++i)
q[i].ans=getsum(low[nd[q[i].x]])-getsum(dfn[nd[q[i].x]]-1);
for(int i=0;i<26;++i)
if(t[u].Vis[i])
DFS(t[u].Vis[i]);
Modify(dfn[u],-1);
}
int main()
{
scanf("%s",ss+1);
int now=0;
for(int i=1,l=strlen(ss+1);i<=l;++i)
{
if(ss[i]>='a'&&ss[i]<='z')
{
if(!t[now].vis[ss[i]-'a'])t[now].vis[ss[i]-'a']=++tot,t[tot].fa=now;
now=t[now].vis[ss[i]-'a'];
}
if(ss[i]=='B')now=t[now].fa;
if(ss[i]=='P'){nd[++n]=now;t[now].lt=n;}
}
for(int i=0;i<=tot;++i)
for(int j=0;j<26;++j)
t[i].Vis[j]=t[i].vis[j];
int Q=read();
GetFail();
for(int i=1;i<=tot;++i)Add(t[i].fail,i);
dfs(0);
for(int i=1;i<=Q;++i)
{
q[i].x=read(),q[i].y=read();
q[i].id=i;
}
sort(&q[1],&q[Q+1]);
for(int i=1,pos=1;i<=Q;i=pos)
{
ql[q[i].y]=i;
while(q[pos].y==q[i].y)pos++;
qr[q[i].y]=pos-1;
}
DFS(0);
for(int i=1;i<=Q;++i)ans[q[i].id]=q[i].ans;
for(int i=1;i<=Q;++i)
printf("%d\n",ans[i]);
return 0;
}
【BZOJ2434】阿狸的打字机(AC自动机,树状数组)的更多相关文章
- 【BZOJ】2434: [Noi2011]阿狸的打字机 AC自动机+树状数组+DFS序
[题意]阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小写 ...
- BZOJ2434: [Noi2011]阿狸的打字机(AC自动机 树状数组)
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 4140 Solved: 2276[Submit][Status][Discuss] Descript ...
- [BZOJ2434][Noi2011]阿狸的打字机 AC自动机+树状数组+离线
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2434 题目中这种多个串匹配的问题,一下子就想到了AC自动机.然后发现如果要建立AC自动机, ...
- [NOI2011]阿狸的打字机 --- AC自动机 + 树状数组
[NOI2011] 阿狸的打字机 题目描述: 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机. 打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现, ...
- BZOJ.2434.[NOI2011]阿狸的打字机(AC自动机 树状数组 DFS序)
题目链接 首先不需要存储每个字符串,可以将所有输入的字符依次存进Trie树,对于每个'P',记录该串结束的位置在哪,以及当前节点对应的是第几个串(当前串即根节点到当前节点):对于'B',只需向上跳一个 ...
- bzoj 2434 阿狸的打字机 - Aho-Corasick自动机 - 树状数组
题目传送门 传送站I 传送站II 题目大意 阿狸有一个打字机,它有3种键: 向缓冲区追加小写字母 P:打印当前缓冲区(缓冲区不变) B:删除缓冲区中最后一个字符 然后多次询问第$x$个被打印出来的串在 ...
- 洛谷P2414 阿狸的打字机 [NOI2011] AC自动机+树状数组/线段树
正解:AC自动机+树状数组/线段树 解题报告: 传送门! 这道题,首先想到暴力思路还是不难的,首先看到y有那么多个,菜鸡如我还不怎么会可持久化之类的,那就直接排个序什么的然后按顺序做就好,这样听说有7 ...
- [NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]
题面 传送门 正文 最暴力的 最暴力的方法:把所有询问代表的字符串跑一遍kmp然后输出 稍微优化一下:把所有询问保存起来,把模板串相同的合并,求出next然后匹配 但是这两种方法本质没有区别,都是暴力 ...
- 【dfs序+AC自动机+树状数组】BZOJ2434-[Noi2011]阿狸的打字机
[题目大意] 输入一个字符串,其中:(1)a..z:在字符串末尾添加当前字符(2)P:输出当前字符串(3)B:从当前字符串末尾删去一个字符. 给出m组查询,输出第i个输出的字符串在第j个输出的字符串内 ...
- bzoj 2434 AC自动机+树状数组
2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 3493 Solved: 1909[Submit][Sta ...
随机推荐
- python爬虫(7)——BeautifulSoup
今天介绍一个非常好用的python爬虫库--beautifulsoup4.beautifulsoup4的中文文档参考网址是:http://beautifulsoup.readthedocs.io/zh ...
- 发送POST测试请求的若干方法
最近在工作中需要测试发送带Json格式body值的HTTP POST请求.起初,我在Linux环境下使用curl命令去发送请求,但是,在发送的过程中却遇到了一些问题,经过一段时间的摸索,发现了以下几种 ...
- PHP中一种sign计算方法
一言不合上代码......... <?php function getsign($data,$key){ $key=MD5("KEY_".$key."_K" ...
- Head First C 笔记
嗨翻c语言 1. 入门 为什么字符从零开始编号? 字符的索引数值表示的是一个偏移量,它表示的是当前所引用的字符与第一个字符之间差多少个字符. 单双引号的区别? 单引号 一个字符,双- 字符串 字符串字 ...
- 教我徒弟Android开发入门(二)
前言: 上一期实现了简单的QQ登录效果,这一期继续对上一期进行扩展 本期的知识点: Toast弹窗,三种方法实现按钮的点击事件监听 正文: Toast弹窗其实很简单,在Android Studio ...
- Bootstrap表单验证
主要用过两个: jqBootstrapValidation: https://github.com/ReactiveRaven/jqBootstrapValidation bootstrapValid ...
- PHP封装的一个单例模式Mysql操作类
掌握满足单例模式的必要条件----三私一公. ①私有的构造方法-为了防止在类外使用new关键字实例化对象. ②私有的成员属性-为了防止在类外引入这个存放对象的属性. ③私有的克隆方法-为了防止在类外通 ...
- 关于UIButton嵌入到UIView点击无反应问题的解决方法和delegate的简单用法示例(转载)
做项目封装UIView的时候碰到的问题,没想到有个哥们儿还写成博客,特此收藏! 问题是这样的,几个界面用到同一个自定义返回按钮,于是就想着把这个按钮单独封装起来,添加一个UIView类,在里面自定义U ...
- POJ - 1797 Heavy Transportation 单源最短路
思路:d(i)表示到达节点i的最大能运输的重量,转移方程d(i) = min(d(u), limit(u, i));注意优先队列应该以重量降序排序来重载小于符号. AC代码 #include < ...
- FineUIPro控件库深度解析
FineUIPro控件库 FineUIPro是一套基于jQuery的专业ASP.NET控件库,始于2008年的开源版FineUI控件库. 当年为了提升项目的开发效率,降低代码复杂度,减少对CSS和Ja ...