• AC自动机


    AC自动机

    define

    tire[i].son[j] 表示第 i 个节点的字母 j 儿子 。

    tire[i].End 表示这个节点是几个字符串的最后一个字母。

    tire[i].next 失配指针。

    insert()

    void insert()
    {
    	int now=0;//当前节点
    	for(int i=1;i<=Len;i++){
    		int ch=str[i]-'a';
    		if(!tire[now].son[ch])tire[now].son[ch]=++num;//如果没出现过就新建一个
    		now=tire[now].son[ch];
    	}
    	tire[now].End++;//节点now作为了一个字符串的终止节点
    }
    

    插入很简单。

    处理失配指针

    next 是找到一个最长的后缀是自己的前缀,这个后缀不能是自己本身。

    考虑 kmp 是怎么求 next 的。

    while(j&&B[i]!=B[j+1])j=nxt[j];
    

    这个 (i)(j) ((j)(i) 短) 同时指向 2 个前缀,并且 (j)(i-1)的前缀。

    如果 (j+1) 不等于 (i) 那么令 (j=nxt[j]) ,就是说如何 (j+1) 接不上 (i) 那么考虑 (j) 的前缀 (j’) ((j’)(j) 的后缀同时也是 (i-1) 的后缀)。

    那么我们也可以在 tire 树上这么求。

    首先如何保证 j 比 i 短呢?

    可以在 tire 树上面 bfs 。

    然后枚举每个点 x 的 son[i]。

    • 如果它有 son[i] ,那么让你儿子 (i) 的失配指针指向你的失配指针的儿子 (i') ,因为你的前缀加一个字母 i 肯定是你加一个字母 i 形成的字符串的前缀。
    • 否则直接让 son[i] 指向 tire[nxt].son[i] ,和 kmp 那个同理就是说如果 (j+1) 接不上 (i) 那么考虑 (j) 的前缀 (j')

    这里有一个疑问,好像缺了个 while ,如果 (j’) 也接不上呢?

    这是一个优化,这里当然可以写一个 while 循环一步一步向上跳,但是常数有点大。

    假设写 while 时会沿着 A->B->C->D 跳。

    先假设之前的 next 和 son[i] 都是对的。

    • 再假设这一条路都接不上字母 i,那么 B,C,D 都会指向 0,然后令 (A.son[i]=B.son[i]) 这肯定也是 0,是对的。
    • 再假设 C,D 能接上,那么 B 自然指向 C ,然后令 (A.son[i]=B.son[i]) 这肯定也是 C,是对的。(因为C有儿子 i,那么它指向自己的儿子)

    这样就能一步到位。

    void prepare()
    {
    	tou=1;wei=0;
    	for(int i=0;i<26;i++)
    	if(tire[0].son[i])
    		que[++wei]=tire[0].son[i];//先把长度为1的入队。
    	while(tou<=wei){
    		int x=que[tou++];
    		for(int i=0;i<26;i++)
    		if(tire[x].son[i]){
    			tire[tire[x].son[i]].next=tire[tire[x].next].son[i];
    			que[++wei]=tire[x].son[i];
    		}else tire[x].son[i]=tire[tire[x].next].son[i];
    	}
    }
    

    计算有多少个T出现在S中

    先把所有的 T 插入 tire树 中,模仿插入的方法,拿 S 在 tire树 上跑一圈(这一圈肯定不会加入新节点,因为对于每个节点,就算后面接不上字母i,那么它的儿子都指向了他的失配节点的儿子)。

    如果跑到一个地方,发现 tire[x].End 不为-1,那么就加上他的贡献,把 tire[x].End 改成 -1,紧接着沿着它的失配指针查跑到节点 0,同时做同样的操作。(如果向上跳的过程中发现 tire[y].End 是 -1,那么就break,因为沿着它的失配指针到达的节点 (z) 都被经历过了)。

    void calc()
    {
    	int now=0;
    	for(int i=1;i<=Len;i++){
    		int ch=str[i]-'a';
    		now=tire[now].son[ch];
    		for(int t=now;t&&tire[t].End!=-1;t=tire[t].next){
    			ans+=tire[t].End;
    			tire[t].End=-1;
    		}
    	}
    }
    
  • 相关阅读:
    SpringCloud Alibaba开篇:SpringCloud这么火,为何还要学习SpringCloud Alibaba?
    SpringBoot整合原生OpenFegin的坑(非SpringCloud)
    Git入门教程,详解Git文件的四大状态
    全世界最强的算法平台codeforces究竟有什么魅力?
    设计模式第二篇,链式方法模式
    matplotlib设置颜色、标记、线条,让你的图像更加丰富
    20行代码实现,使用Tarjan算法求解强连通分量
    深入理解SVM,详解SMO算法
    手把手教你配置git和git仓库
    设计模式 | Catalog设计模式,抵御业务方需求变动
  • 原文地址:https://www.cnblogs.com/zYzYzYzYz/p/14448348.html
Copyright © 2020-2023  润新知