字符串总结-三大“自动机”

复习笔记-字符串算法

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\n",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\n",ans);
75     return 0;
76 }
View Code

 

posted @ 2019-03-07 16:27 Noire02 阅读(...) 评论(...) 编辑 收藏
Live2D