2021.11.10 fail树

https://blog.csdn.net/niiick/article/details/87947160

1. AC自动机与fail树的神奇关系

1.1 AC自动机

匹配一个文本字符串S,从i开始,一步一步向fail[i]跳,直到根,每匹配一次最多转跳m个T(假设一共有m个T),时间复杂度最大是 \(O(m^2)\),建树的复杂度为 \(O(n)\)。

对于Luogu P5357https://www.luogu.com.cn/problquanzhiem/P5357,对于 \(1\le n \le 2 \times 10^5\),T的长度总和不超过 \(2 \times 10^5\),\(|S| \le 2 \times 10^5\)。

AC自动机肯定挂,没得说。

1.2 fail树

如果把fail指针反过来,变为从 fail[i]指向i,那么每次求T在S中出现了几次,只需要把S的所有节点标上1,求一下T最后的节点所在的子树的权值之和。如果乐意,还可以求个dfs序,然后用树状数组直接求出子树权值之和,修改和查询时间复杂度均为 \(O(\log n)\)。

2.练习题

https://www.luogu.com.cn/problem/P5357

1.1里出现的那道题。

题意:

如1.1。

分析:

如1.2。

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std; const int N=2e6+10;
int n,tot,cnt[200010],ans,ru[N],fa[N];
char s[N],t[N];
struct node{
int son[26],fail,flag,ans;
void clear(){
memset(son,0,sizeof(son));
flag=fail=ans=0;
}
}trie[N];
queue<int>q; inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
void insert(char *s,int idi){
int u=0,len=strlen(s);
//cout<<len<<endl;//
for(int i=1;s[i];i++){
if(trie[u].son[s[i]-'a']==0)trie[u].son[s[i]-'a']=++tot;//,
//cout<<"u "<<u<<" s[i]-'a' "<<s[i]-'a'<<" tot "<<tot<<endl;
u=trie[u].son[s[i]-'a'];
//cout<<u<<" ";
}
if(!trie[u].flag)trie[u].flag=idi;
fa[idi]=trie[u].flag;
//cout<<endl;
//cout<<idi<<" fa "<<fa[idi]<<" flag "<<trie[u].flag<<" u "<<u<<endl;
}
void build(){
for(int i=0;i<26;i++)if(trie[0].son[i])q.push(trie[0].son[i]);
while(!q.empty()){
int x=q.front();q.pop();
int faili=trie[x].fail;
for(int i=0;i<26;i++){
if(trie[x].son[i])
trie[trie[x].son[i]].fail=trie[trie[x].fail].son[i],
q.push((trie[x].son[i])),
++ru[trie[trie[x].son[i]].fail];
else trie[x].son[i]=trie[trie[x].fail].son[i];
}
}
}
void topu(){
for(int i=0;i<=tot;i++)if(!ru[i])q.push(i);
while(!q.empty()){
int x=q.front();q.pop();
cnt[trie[x].flag]=trie[x].ans;
int faili=trie[x].fail;
--ru[faili];
trie[faili].ans+=trie[x].ans;
if(!ru[faili])q.push(faili);
}
}
void query(char *t){
int u=0,len=strlen(t);
for(int i=1;t[i];i++)
u=trie[u].son[t[i]-'a'],++trie[u].ans;
} int main(){
//memset(&trie,0,sizeof(trie));
n=read();
for(int i=1;i<=n;i++)scanf("%s",s+1),insert(s,i);
build();
//for(int i=1;i<=n;i++)cout<<fa[i]<<" ";cout<<endl<<endl;
scanf("%s",t+1);
query(t);
topu();
for(int i=1;i<=n;i++)printf("%d\n",cnt[fa[i]]);
return 0;
}

https://www.luogu.com.cn/problem/P2414

题意:

给定一个字符串,按B最后一个字母消失,按P打印之前的字母并换行。给定 \((x,y)\) 求x在y中出现多少次。

分析:

\(1 \le n \le 10^5\),\(1 \le m \le 10^5\),用自动机肯定会超时。如果我们先把y拍拍序,对于同一个y的二元组一起查询会比单个按原来顺序查询快很多,参考1.2。

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std; const int N=1e5+10;
int m,cnt,ind,dfsx[N],sizei[N],head[N],ans[N];
char s[N];
struct queryi{
int x,y,id;
bool operator <(const queryi &b)const{
return y==b.y?x<b.x:y<b.y;
}
}query[N];
struct node{
int to,next;
}a[N]; inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
inline void add(int u,int v){
++cnt;
a[cnt].to=v;
a[cnt].next=head[u];
head[u]=cnt;
}
inline void dfs(int x){
dfsx[x]=++ind;
sizei[x]=1;
for(int i=head[x];i;i=a[i].next){
int v=a[i].to;
dfs(v);
sizei[x]+=sizei[v];
}
}
struct AC{
int t[N][26],id[N],fail[N],tot=0,top=0,fa[N];
queue<int>q;
void insert(char *s){
int len=strlen(s),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(s[i]=='B')u=fa[u];
else if(s[i]=='P')id[++top]=u;
else{
if(!t[u][v])t[u][v]=++tot,fa[tot]=u;
u=t[u][v];
}
}
}
void build(){
for(int i=0;i<26;i++)if(t[0][i])q.push(t[0][i]);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;i++){
if(t[u][i]){
fail[t[u][i]]=t[fail[u]][i];
q.push(t[u][i]);//,add(fail[t[u][i]],t[u][i]);
}else t[u][i]=t[fail[u]][i];
}
}
for(int i=1;i<=tot;i++)add(fail[i],i);
}
}trie;
struct BIT{
int t[N*5];
int lowbit(int x){
return x&-x;
}
void add(int x,int k){
for(int i=x;i<=ind;i+=lowbit(i))t[i]+=k;
}
int query(int x){
int fin=0;
for(int i=x;i>0;i-=lowbit(i))fin+=t[i];
return fin;
}
void clear(){
memset(t,0,sizeof(t));
}
}tree;
inline void solve(char *s){
int len=strlen(s),u=0,js=0,now=1;
for(int i=0;i<len;i++){
//cout<<"i "<<i<<" u "<<u<<endl;
int v=s[i]-'a';
if(s[i]=='B')tree.add(dfsx[u],-1),u=trie.fa[u];
else if(s[i]=='P'){
++js;
while(query[now].y==js){
int idi=query[now].id,x=trie.id[query[now].x];
ans[idi]+=tree.query(dfsx[x]+sizei[x]-1)-tree.query(dfsx[x]-1);
++now;
}
}else u=trie.t[u][v],tree.add(dfsx[u],1);
}
} int main(){
scanf("%s",s);
int len=strlen(s);
trie.insert(s);
//cout<<"Case 1"<<endl;
trie.build();
dfs(0);
m=read();
for(int i=1;i<=m;i++)query[i].x=read(),query[i].y=read(),query[i].id=i;
sort(query+1,query+m+1);
//for(int i=1;i<=m;i++)cout<<"id "<<query[i].id<<" x "<<query[i].x<<" y "<<query[i].y<<endl;
solve(s);
for(int i=1;i<=m;i++)cout<<ans[i]<<endl;
return 0;
}

https://www.luogu.com.cn/problem/P3966

题意:

小张最近在忙毕设,所以一直在读论文。一篇论文是由许多单词组成但小张发现一个单词会在论文中出现很多次,他想知道每个单词分别在论文中出现了多少次。

分析:

直接给每个字符串加入trie时所经过的每个节点加1,求出每个字符串最后节点的fail树的权值之和。

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std; const int N=2e6+10;
int n,trie[N][26],cnt,end[N],fail[N],size[N],q[N];
char s[N]; inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
} void insert(char *s,int id){
int u=0;
for(int i=1;s[i];i++){
int v=s[i]-'a';
if(!trie[u][v])trie[u][v]=++cnt;
u=trie[u][v];
++size[u];
}
end[id]=u;
}
void build(){
//queue<int>q;
int head=0,tail=0;
for(int i=0;i<26;i++)if(trie[0][i])q[++tail]=trie[0][i];
while(head<tail){
int u=q[++head];
for(int i=0;i<26;i++){
int v=trie[u][i],faili=fail[u];
if(v)fail[v]=trie[faili][i],q[++tail]=v;
else trie[u][i]=trie[faili][i];
}
}
}
void solve(){
for(int i=cnt;i>=0;i--)size[fail[q[i]]]+=size[q[i]];
for(int i=1;i<=n;i++)cout<<size[end[i]]<<endl;
} int main(){
n=read();
for(int i=1;i<=n;i++)scanf("%s",s+1),insert(s,i);
build();
solve();
return 0;
}

2021.11.10 fail树的更多相关文章

  1. 2021.11.10 [POI2000]病毒(AC自动机)

    2021.11.10 [POI2000]病毒(AC自动机) https://www.luogu.com.cn/problem/P2444 题意: 二进制病毒审查委员会最近发现了如下的规律:某些确定的二 ...

  2. 2021.11.10 P5231 [JSOI2012]玄武密码(AC自动机)

    2021.11.10 P5231 [JSOI2012]玄武密码(AC自动机) https://www.luogu.com.cn/problem/P5231 题意: 给出字符串S和若干T,求S与每个T的 ...

  3. 2021.11.09 P2292 [HNOI2004]L语言(trie树+AC自动机)

    2021.11.09 P2292 [HNOI2004]L语言(trie树+AC自动机) https://www.luogu.com.cn/problem/P2292 题意: 标点符号的出现晚于文字的出 ...

  4. 2021.12.10 P5041 [HAOI2009]求回文串(树状数组求逆序对)

    2021.12.10 P5041 [HAOI2009]求回文串(树状数组求逆序对) https://www.luogu.com.cn/problem/P5041 题意: 给一个字符串 \(S\) ,每 ...

  5. 2021.11.30 eleveni的水省选题的记录

    2021.11.30 eleveni的水省选题的记录 因为eleveni比较菜,eleveni决定先刷图论,再刷数据结构,同时每天都要刷dp.当然,对于擅长的图论,eleveni决定从蓝题开始刷.当然 ...

  6. 2021.11.11 EXKMP

    2021.11.11 EXKMP https://www.luogu.com.cn/problem/P5410 下标以1开头: #include<cstdio> #include<i ...

  7. 2021.11.11 P4052 [JSOI2007]文本生成器(AC自动机+DP)

    2021.11.11 P4052 [JSOI2007]文本生成器(AC自动机+DP) https://www.luogu.com.cn/problem/P4052 题意: JSOI 交给队员 ZYX ...

  8. 2021.11.09 P4824 [USACO15FEB]Censoring S与P3121 [USACO15FEB]Censoring G(KMP&&AC自动机)

    2021.11.09 P4824 [USACO15FEB]Censoring S与P3121 [USACO15FEB]Censoring G(KMP&&AC自动机) https://w ...

  9. BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Sta ...

随机推荐

  1. 专访 KubeVela 核心团队:如何简化云原生复杂环境下的应用交付和管理

    作者 | Infoq Tina 背景 12 月 9 日,在 2021 年 KubeCon 云原生技术峰会上,CNCF 开源项目 KubeVela 宣布推出了 1.2 版本. KubeVela 是一个简 ...

  2. tea加密算法及其变种的研究

    tea 介绍 "TEA" 的全称为"Tiny Encryption Algorithm" 是1994年由英国剑桥大学的David j.wheeler发明的. T ...

  3. 如何通过HibernateDaoSupport将Spring和Hibernate?

    用 Spring 的 SessionFactory 调用 LocalSessionFactory.集成过程分三步: 配置 the Hibernate SessionFactory. 继承 Hibern ...

  4. 创建Maven web工程

    ---恢复内容开始--- 第一步,启动Eclipse,依次打开菜单[File][New][Other] 找到目录Maven,选择Maven Project, 选择一个Archetype.这里创建Web ...

  5. Go 语言 结构体

    Go 语言 结构体 引言Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型结构体是由一系列具有相同类型或不同类型的数据构成的数据集合结构体表示一项记录,比如保存图书 ...

  6. three设置相机面向模型

    模型自身position属性 为(0,0,0),但并不在场景中心, 可能建模的原因,位置放到中心但设置了偏移(不一定). 解决方法: 主要代码 let box = new THREE.Box3(); ...

  7. 《自动控制原理》个人笔记(来自ppt课件)

    控制的含义 控制(CONTROL)----某个主体使某个客体按照一定的目的动作.主体–人:人工控制: 机器:自动控制客体–指一件物体,一套装置,一个物化过程,一个特定系统. 人工控制与自动控制 人在控 ...

  8. C语言break,return

    C语言break,continue,return的相似与区别 相同点: 都改变了程序的执行流程 区别是:break    用于循环和switch分支,跳出它所在分支或循环体到它所在的模块的      ...

  9. C++ | 再探智能指针(shared_ptr 与 weak_ptr)

    上篇博客我们模拟实现了 auto_ptr 智能指针,可我们说 auto_ptr 是一种有缺陷的智能指针,并且在C++11中就已经被摈弃掉了.那么本章我们就来探索 boost库和C++11中的智能指针以 ...

  10. C++ | 虚函数产生条件

    虚函数产生的条件 能否成为虚函数主要有以下两种判断依据,如果以下两种条件均满足,则具有成为虚函数的条件. 1.虚函数机制为动多态提供支持,而虚函数表中存放着虚函数的地址.因此虚函数必须是可以取地址的函 ...