AC 自动机和字典树

一,trie 字典树

经典使用(字典查找有无)

原理: 每个字母作为一个节点,形似一个字典
插入: 沿着前面建立好的字母,知道第一个没有的字母,开辟新的节点记录即可
查找: 沿着树边,直接找到叶子上的最后一个字母,输出其cnt即可
几点注意:

  • 1,根节点不存任何字母
  • 2,一个cnt记录可能出现很多次的单词
  • 3,存图的方式是邻接表,方便快速了解字母的有无
  • 4,idx++,根节点是唯一的0
  • 5,单个字母通过映射到0-25
int son[N][26];
int idx;
int cnt[N];
char str[N];

void insert(char str[])
{
    int p=0;
    for (int i = 0; str[i]; i ++ )
    {
        int u=str[i]-'a';
        if (!son[p][u])son[p][u]=++idx;
        p=son[p][u];
    }
    cnt[p]++;
}

int query(char str[])
{
    int p=0;
    for (int i = 0; str[i]; i ++ )
    {
        int u=str[i]-'a';
        if(!son[p][u])return 0;
        p=son[p][u];
    }
    return cnt[p];
}

经典扩展(最大异或和)

思路:
1,枚举每个数,贪心的思考易知,最高位尽可能是1最好
2,我们对每个数建立01(二进制)字典树,易知每个节点最多只有0或者1两个儿子,所以第二维开size2
3,对于枚举的每个数,从根节点开始遍历,如果当前位可以异或出1,我们就选择这个子树向下(答案累计)
4,到达叶子节点,直接获得最值,和全局最值比较即可

注意:

  • 由于建立了2进制索引,所以第一维,开到 N ∗ 31 N*31 N31 的可能最值
  • 注意建树从高位起建,我们需对高位优先判断
void insert(int x)
{
    int p=0;
    for (int i = 30; ~i ; i -- )
    {
        int u=(x>>i)&1;
        if(!son[p][u])son[p][u]=++idx;
        p=son[p][u];
    }
}

int query(int x)
{
    int  p=0;
    int res=0;
    for (int i = 30; ~i ; i -- )
    {
        int u=(x>>i&1);
        if(!son[p][!u]) p=son[p][u];
        else {
            p=son[p][!u];
            res+=1<<i;
        }
    }
    return res;
}

AC Code 及题目

深度练习
P2580 于是他错误的点名开始了
P4551 最长异或路径

二,AC 自动机

AC 自动机入门
AC 自动机进阶
符合我的码风的讲解(实操性比较好)

  • AC自动机= Trie + KMP ,可优化为Trie图
    KMP : O(n) 求出某一个单词 出现在哪些地方 出现次数
    AC自动机 : O(n) 求出每一个单词 出现在哪些地方 出现次数
    故AC自动机是在 KMP 算法的基础上,将 KMP 匹配的过程,移植到字典树上

  • 需要理解AC自动机是一种极度削减版的有限状态自动机

1,二次认识 KMP

void kmp()
{
    for (int j = 0,i = 2; i <= n; i ++ )
    {
        while (j&&p[j+1]!=p[i])j=nxt[j];
        if(p[j+1]==p[i])j++;
        nxt[i]=j;
    }
}

所以 while 这一行开始的时候的 j是上一轮赋给 next[i-1] 时的 j
所以 KMP 其实质:先求next[0] -> next[i-1] 即为前 i-1 组的信息 ,在前 i-1 组的信息上再去求 next[i]
所以:所谓的 j 指针其实可以根据 nxt 索引出来,这为树上节点编号的进入提供了有利条件

2,二次认识失配数组

同理,其失配函数以该位置为后缀的子串和从1开始长度相同的前缀的长度?不不是下标

  • 字典树的节点编号不再满足意为数组上的连续性,故应当保存下标

3,二次认识匹配过程

  • 匹配的新定义

1,字典树的字母是以方向界定的,节点不是字母,而是方向实体化

2,所以说在字典树上的匹配需要以方向来判别,至于有没有匹配成功,则是以该方向有没有点来界定的。

3,每次沿着Trie树匹配,匹配到当前位置没有匹配上时,直接跳转到失配指针所指向的位置继续进行匹配。

  • Trie树的失配指针的求法

1,Trie树的失配指针是指向:沿着其父节点 的 失配指针,一直向上,直到找到拥有当前这个字母的子节点 的节点 的那个子节点

2,值得一提的是,第二层的所有节点的失配指针,都要直接指向Trie树的根节点。(这是在自动归零的情况下有失计较的一点)

  • 树上匹配的模式

1,我们引入一个树上分层的概念,就是以从字符串头开始的前缀的长度为层

2,我们根据层的次数进行逐层扩展,这样就能符和KMP中的 在前 i-1 组的信息上再去求 next[i] 过程,这是一个逐层扩展的方法,所以说,应采用具有两重性和单调性的队列搜索算法BFS来实现

int work ()
{
    int res= 0;
    for (int i= 0,j = 0 ;str[i];i++)
    {
        int t = str[i] - 'a';  // 获取方向
        j= tr[j][t];           // 索引已匹配位置的同取向字典树节点
        int p = j;
        while(p)
        {
            if (st[p])break;
            res+= cnt[p];
            cnt[p]=0;
            st[p]= 1;
            p=ne[p];
        }
    }
    return res;
}

4,多模匹配的典例

  • 一个文本串,和多个模式串匹配,求解匹配每个模式串的出现次数(常数极大)
  • 分析:
    1,所谓出现,就是求解所有前缀的后缀为模式串的子串个数
    2,失配指针是DAG,回归fail指针定义,可以采用食物链计数的方法,逐层汇聚(这简直是「すばらしい」!)
    3,注意活用DAG性质,高速跳转字典树上节点
    4,匹配主串的时候,只需要在字典树按照深度遍历,并在经过的节点上计数+1,无论有没有这个节点都这么做

step 1,建立失配边DAG,并对主串匹配

for (int i= 0;str[i];i++)
    {
        int t = str[i] - 'a';
        u=tr[u][t];
        f[u]++;
    }
    for (int i= 1;i<=idx;i++)add(ne[i],i);

step 2 深搜,进行食物链计数累计

  • 我这里字典树根为0,要防返祖
  • 凡是深度大于模式串长的都是被包含的子串
  • 要找所有单词中某个单词出现的次数,其实就是看在所有的前缀的后缀中某个单词出现的次数
  • 建反边深搜可以使得答案集中且更为便于理解,正边累计也可以
void dfs(int x)
{
    for (int i=h[x];~i;i=nxt[i])
    {
        int j=e[i];
        if(j)dfs(j),f[x]+=f[j];
    }
}
dfs(0);

获得匹配数目的奥秘

有点离线的感觉,记录节点编号,再离线输出,是贡献累计型的套路了属于

void insert (int num,char opt[])
{
    int p = 0;
    for (int i= 0;opt[i];i++)
    {
        int t = opt[i] - 'a';
        if (!tr[p][t])tr[p][t]= ++idx;
        p=tr[p][t];
    }
    id[num]= p ;
}
for (int i=1;i<=n;i++)printf("%d\n",f[id[i]]);

AC 自动机基础模板刷题记录 No.02012501

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值