2021.11.10 fail树
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树的更多相关文章
- 2021.11.10 [POI2000]病毒(AC自动机)
2021.11.10 [POI2000]病毒(AC自动机) https://www.luogu.com.cn/problem/P2444 题意: 二进制病毒审查委员会最近发现了如下的规律:某些确定的二 ...
- 2021.11.10 P5231 [JSOI2012]玄武密码(AC自动机)
2021.11.10 P5231 [JSOI2012]玄武密码(AC自动机) https://www.luogu.com.cn/problem/P5231 题意: 给出字符串S和若干T,求S与每个T的 ...
- 2021.11.09 P2292 [HNOI2004]L语言(trie树+AC自动机)
2021.11.09 P2292 [HNOI2004]L语言(trie树+AC自动机) https://www.luogu.com.cn/problem/P2292 题意: 标点符号的出现晚于文字的出 ...
- 2021.12.10 P5041 [HAOI2009]求回文串(树状数组求逆序对)
2021.12.10 P5041 [HAOI2009]求回文串(树状数组求逆序对) https://www.luogu.com.cn/problem/P5041 题意: 给一个字符串 \(S\) ,每 ...
- 2021.11.30 eleveni的水省选题的记录
2021.11.30 eleveni的水省选题的记录 因为eleveni比较菜,eleveni决定先刷图论,再刷数据结构,同时每天都要刷dp.当然,对于擅长的图论,eleveni决定从蓝题开始刷.当然 ...
- 2021.11.11 EXKMP
2021.11.11 EXKMP https://www.luogu.com.cn/problem/P5410 下标以1开头: #include<cstdio> #include<i ...
- 2021.11.11 P4052 [JSOI2007]文本生成器(AC自动机+DP)
2021.11.11 P4052 [JSOI2007]文本生成器(AC自动机+DP) https://www.luogu.com.cn/problem/P4052 题意: JSOI 交给队员 ZYX ...
- 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 ...
- BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]
2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 2545 Solved: 1419[Submit][Sta ...
随机推荐
- docker基础命令,常用操作
docker基础命令 使用docker镜像 获取镜像 从docker registry获取镜像的命令是docker pull.命令格式是: docker pull [选项][docker regist ...
- python中文及符号检测工具带GUI界面
import tkinter import webbrowser import re #本程序是一个中文字符和中文检测工具 #中文字符自己添加,我只添加了一点 #输入字符串,点击检查文本即可判断有没有 ...
- Java多线程之线程同步【synchronized、Lock、volatitle】
线程同步 线程同步:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多. ...
- 手把手带你使用Paint in 3D和Photon撸一个在线涂鸦画板
Paint in 3D Paint in 3D用于在游戏内和编辑器里绘制所有物体.所有功能已经过深度优化,在WebGL.移动端.VR 以及更多平台用起来都非常好用! 它支持标准管线,以及 LWRP.H ...
- 序列化和反序列化&持久化
java序列化与反序列化全讲解 之前一知半解的,对于序列化的概念,为啥用,哪里用也不清楚,现在深入了解协议,先把序列化这个这个概念和和使用场景搞清楚
- InnoDB什么时候会锁表?
我们常常说InnoDB是行锁,但是这里介绍一下它锁表的情况. InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的.InnoDB ...
- 面试问题之C++语言:类模板声明与定义为何不能分开
C++中每个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的.只有模板被真正使用的时候,编译器才知道,模板 ...
- Java Output流写入包装问题
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); OutputStreamWriter output ...
- Java 中你怎样唤醒一个阻塞的线程?
在 Java 发展史上曾经使用 suspend().resume()方法对于线程进行阻塞唤醒,但 随之出现很多问题,比较典型的还是死锁问题. 解决方案可以使用以对象为目标的阻塞,即利用 Object ...
- 简述 Mybatis 的插件运行原理,以及如何编写一个插件。
Mybatis 仅可以编写针对 ParameterHandler.ResultSetHandler. StatementHandler.Executor 这 4 种接口的插件,Mybatis 使用 J ...