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 ...
随机推荐
- IDEA版部署离线ArcGIS api for JavaScript
痛苦!折磨! 这两天发现IDEA 做ArcGIS api 开发比EClipse好用(重点是没有JS提示).但引入的ArcGIS api 一直报错,说找不到.这两天思来想去就是不知道为什么找不到loca ...
- SQL获取当前时间(日期)
--获取当前日期(如:yyyymmdd)select CONVERT (nvarchar(12),GETDATE(),112) --获取当前日期(如:yyyymmdd hh:MM:ss)select ...
- volatile 有什么用?能否用一句话说明下 volatile 的应用场景?
volatile 保证内存可见性和禁止指令重排. volatile 用于多线程环境下的单次操作(单次读或者单次写).
- Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?
配置变更JDK 版本升级第三方类库升级响应式 Spring 编程支持HTTP/2 支持配置属性绑定更多改进与加强-
- IOC的优点是什么?
IOC 或 依赖注入把应用的代码量降到最低.它使应用容易测试,单元测试不再需要单例和JNDI查找机制.最小的代价和最小的侵入性使松散耦合得以实现.IOC容器支持加载服务时的饿汉式初始化和懒加载.
- JVM组成结构以及各部分的功能
Java虚拟机主要分为以下五个区: 一.方法区(METHOD AREA): 1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型 ...
- 什么是 MyBatis 的接口绑定?有哪些实现方式?
接口绑定,就是在 MyBatis 中任意定义接口,然后把接口里面的方法和 SQL 语句绑 定, 我们直接调用接口方法就可以,这样比起原来了 SqlSession 提供的方法我们可 以有更加灵活的选择和 ...
- memcached 的 cache 机制是怎样的?
Memcached 主要的 cache 机制是 LRU(最近最少用)算法+超时失效.当您存 数据到 memcached 中,可以指定该数据在缓存中可以呆多久 Which is forever, or ...
- Windows10安装PHP7+Apache 2.4
下载下面的文件 httpd-2.4.39-win64-VC15.zip php-7.3.4-Win32-VC15-x64.zip 如果下载失效,从这里下载 https://windows.php.ne ...
- 深入 x64
本篇原文为 X64 Deep Dive,如果有良好的英文基础的能力,可以点击该链接进行阅读.本文为我个人:寂静的羽夏(wingsummer) 中文翻译,非机翻,著作权归原作者所有. 由于原文十 ...