zoukankan      html  css  js  c++  java
  • 字符串总结-三大“自动机”

    复习笔记-字符串算法

    AC自动机

    本质

    在字典树上进行KMP匹配

    实现思路

    正常建立Trie树

    处理失配指针

    在AC自动机中,失配指针fail和KMP的next是一样的作用,如下图(from SuperJvRuo)

     

     不难看出fail指针的构造方法:通过Bfs,设这个节点上的字母为c,沿着他父亲的fail走,直到走到一个节点,他的儿子中也有字母为c的节点,我们就找到了所求的fail。

    匹配

    类比KMP,我们可以先在树上走,如果失去匹配,就跳fail指针

    优化

    进行路径压缩,把Trie树变成图

    代码实现

    #include <bits/stdc++.h>
    
    const int maxn = 1000010;
    char ss[maxn];
    
    struct node {
        node *ch[26];
        int exist;
        node *fail;
        node(): exist(0), fail(0) {
            memset(ch, 0, sizeof ch);
        }
    };
    
    node *root = new node;
    
    void insert(char *s) {//字典树建立
        node *p = root;
        for (int i = 0; s[i]; ++i) {
            int x = s[i] - 'a';
            if (p -> ch[x] == NULL) p -> ch[x] = new node;
            p = p -> ch[x];
        }
        ++p -> exist;
    }
    
    void get_fail() {//处理失配指针
        node *p, *tmp;
        std::queue<node*> q;
        q.push(root);
        while (!q.empty()) {
            tmp = q.front();
            q.pop();
            for (int i = 0; i < 26; ++i) 
                if (tmp -> ch[i]) {
                    if (tmp == root) {
                        tmp -> ch[i] -> fail = root;
                    } else {
                        p = tmp -> fail;
                        while (p) {
                            if (p -> ch[i]) {
                                tmp -> ch[i] -> fail = p -> ch[i];
                                break;
                            }
                            p = p -> fail;
                        }
                        if (p == NULL) tmp -> ch[i] -> fail = root;
                    }
                    q.push(tmp -> ch[i]);
                }
        }
    }
    
    int AC(char *s) {//匹配
        int cnt = 0;
        int l = strlen(s);
        node *p = root;
        for (int i = 0; i < l; ++i) {
            int x = s[i] - 'a';
            while (!p -> ch[x] and p != root) p = p -> fail;
            p = p -> ch[x];
            if (!p) p = root;
            node *tmp = p;
            while (tmp != root) {
                if (tmp -> exist >= 0) cnt += tmp -> exist, tmp -> exist = -1;
                else break;
                tmp = tmp -> fail;
            }
        }
        return cnt;
    }
    
    int main() {
        int n;
        scanf("%d", &n);
        while (n--) {
            scanf("%s", ss);
            insert(ss);
        }
        get_fail();
        scanf("%s", ss);
        printf("%d", AC(ss));
        return 0;
    }
    View Code

      

     回文自动机

    不是很熟

    本质

    是两棵回文树。其中一个维护长度为奇数的字符串,另一个是长度为偶数的字符串

    结构

    • 回文自动机的每一条边代表一个字符。对于每一条边,它的起点字符串左右都加上这个字符就得到终点的字符串
    • 回文自动机的每一个节点代表一个字符串,它的每一个子节点都是它在左右两端分别加上相同的字符得到的
    • 回文自动机的每一个节点都有一个fail指针,它指向这个字符串最长满足回文的后缀的节点。如果没有,就指向根。

    建树

    直接在字符串两端进行扩展。每次要加入一个点的时候,先跳最后加入的一个节点的fail指针,直到满足节点字符等于新加入字符。然后加入新节点,把刚刚找到的那个节点的对应边连到新节点上, 更新节点维护的所有值

    代码实现

     1 #include<cstdio>
     2 
     3 const int maxn=555555;
     4 
     5 int cnt=1;
     6 char s[maxn];
     7 int son[maxn][26];
     8 int len[maxn],fail[maxn];
     9 
    10 int new_node(int length)
    11 {
    12     len[++cnt]=length;
    13     return cnt;
    14 }
    15 
    16 int get_fail(char T[],int pre,int now)
    17 {
    18     while(T[now-len[pre]-1]!=T[now])
    19         pre=fail[pre];
    20     return pre;
    21 }
    22 
    23 void build(char T[])
    24 {
    25     int last=0;
    26     len[0]=0,len[1]=-1;
    27     fail[0]=1,fail[1]=1;
    28     for(int i=1;T[i];i++)
    29     {
    30         int cur=get_fail(T,last,i);
    31         if(!son[cur][T[i]-'a'])
    32         {
    33             int now=new_node(len[cur]+2);
    34             fail[now]=son[get_fail(T,fail[cur],i)][T[i]-'a'];
    35             son[cur][T[i]-'a']=now;
    36         }
    37         last=son[cur][T[i]-'a'];
    38     }
    39 }
    40 
    41 int main()
    42 {
    43     scanf("%s",s+1);
    44     build(s);
    45     printf("%d
    ",cnt-1);
    46     return 0;
    47 }
    View Code

     

    后缀自动机

    不是很熟+1

    结构

    一个DAG,满足从根出发走到所有结束点的所有路径都是字符串的一个后缀

    构造过程

    每次尽可能利用上一次的状态得到新的状态。我也说不明白,背的板子。。

    用途

    可以解决哈希解决不了的所有字符串问题。广义后缀自动机还可以解决其他很多问题。

    代码实现

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 
     5 typedef long long ll;
     6 const int maxn=1111111;
     7 
     8 struct node
     9 {
    10     int son[26];
    11     int len,link;
    12 }sam[maxn<<1];
    13 
    14 int cnt=1,last=1;
    15 int size[maxn<<1];
    16 
    17 void insert(int ch)
    18 {
    19     int cur=++cnt;
    20     sam[cur].len=sam[last].len+1;
    21     int p=last;
    22     while(p&&!sam[p].son[ch])
    23     {
    24         sam[p].son[ch]=cur;
    25         p=sam[p].link;
    26     }
    27     if(!p) sam[cur].link=1;
    28     else
    29     {
    30         int q=sam[p].son[ch];
    31         if(sam[p].len+1==sam[q].len) sam[cur].link=q;
    32         else
    33         {
    34             int clone=++cnt;
    35             sam[clone].len=sam[p].len+1;
    36             sam[clone].link=sam[q].link;
    37             memcpy(sam[clone].son,sam[q].son,sizeof(sam[q].son));
    38             while(p&&sam[p].son[ch]==q)
    39             {
    40                 sam[p].son[ch]=clone;
    41                 p=sam[p].link;
    42             }
    43             sam[q].link=sam[cur].link=clone;
    44         }
    45     }
    46     size[cur]=1;
    47     last=cur;
    48 }
    49 
    50 ll ans;
    51 char s[maxn];
    52 int tong[maxn];
    53 int topo[maxn<<1];
    54 
    55 int main()
    56 {
    57     scanf("%s",s+1);
    58     int len=strlen(s+1);
    59     for(int i=1;i<=len;i++)
    60         insert(s[i]-'a');
    61     for(int i=1;i<=cnt;i++)
    62         tong[sam[i].len]++;
    63     for(int i=1;i<=len;i++)
    64         tong[i]+=tong[i-1];
    65     for(int i=cnt;i>=1;i--)
    66         topo[tong[sam[i].len]--]=i;
    67     for(int i=cnt;i>=1;i--)
    68     {
    69         int now=topo[i];
    70         size[sam[now].link]+=size[now];
    71         if(size[now]>1)
    72             ans=std::max(ans,1LL*size[now]*sam[now].len);
    73     }
    74     printf("%lld
    ",ans);
    75     return 0;
    76 }
    View Code

     

  • 相关阅读:
    地图校正方法心得
    投影的心得点滴
    android 打包 apk keystore
    scp命令详解
    ubuntu11.10真机调试nopermissions
    android adb server is out of date
    ubuntu删除默认jdk
    android 运行 错误 总结
    android file .apk is not a valid zip file adb install
    ubuntu系统目录结构
  • 原文地址:https://www.cnblogs.com/Juruo1103/p/10460784.html
Copyright © 2011-2022 走看看