后缀自动机

本文详细介绍了SAM(后缀自动机)算法的基本原理及多种应用场景,包括查找最长公共子串、统计不同子串数量、寻找第K大的子串等,并提供了具体的实现代码示例。
int last,num;
struct node{
    int fa,len,sum;
    int son[26];
}t[maxn*2];



void extend(int c){      //每次向SAM中插入一个元素c
    p=last,np=++num;
    t[np].len=t[p].len+1;np=num;cc[np]=1;
    while(p&&!t[p].son[c])t[p].son[c]=np,p=t[p].fa;
    if(!p)t[np].fa=1;
    else{
        q=t[p].son[c];
        if(t[p].len+1==t[q].len)t[np].fa=q;
        else{
            nq=++num;
            t[nq]=t[q];
            t[num].len=t[p].len+1;
            t[q].fa=t[np].fa=nq;
            while(p&&t[p].son[c]==q)t[p].son[c]=nq,p=t[p].fa;
        }
    }
    last=np;
}


fo(i,1,n)extend(s[i]-'a');

应用

1、两个串的最长公共子串

建出A串的后缀自动机,然后B串在后缀自动机上跑。

链接:https://www.nowcoder.com/questionTerminal/181a1a71c7574266ad07f9739f791506
来源:牛客网

#include<iostream>
#include<string>
using namespace std;
int main()
{
    string a, b;
    while (cin >> a >> b)
    {
        if (a.size() > b.size())
            swap(a, b);
        string str_m;//存储最长公共子串
        for (int i = 0; i < a.size(); i++)
        {
            for (int j = i; j < a.size(); j++)
            {
                string temp = a.substr(i, j - i + 1);
                    if (int(b.find(temp))<0)
                    break;
                else if (str_m.size() < temp.size())
                    str_m = temp;
            }
        }
        cout << str_m << endl;
    }
    return 0;+

2、找不同的子串的个数

方法一:用dfs处理处每个点能扩展出多少个字符串s u m [ x ] = ∑ s u m [ t [ x ] . s o n [ i ] ] + 1 sum[x]=\sum sum[t[x].son[i]]+1sum[x]=∑sum[t[x].son[i]]+1,其实可以不用dfs,拓扑一下(按len从小到大)然后倒着做。最后sum[1]就是所有子串的个数。
方法二:a n s = ∑ t [ x ] . l e n − t [ t [ x ] . f a ] . l e n ans=\sum t[x].len-t[t[x].fa].lenans=∑t[x].len−t[t[x].fa].len,这里运用了性质1,因为节点i表示的字符串大小范围是从len[fa[i]]+1…len[i]的,那么包含不同串的个数=l e n [ i ] − ( l e n [ f a [ i ] ] + 1 ) + 1 = l e n [ i ] − l e n [ f a [ i ] ] len[i]-(len[fa[i]]+1)+1=len[i]-len[fa[i]]len[i]−(len[fa[i]]+1)+1=len[i]−len[fa[i]]

3、找第K大的子串

1、找不同串的第K大:预处理出每个状态可以构出多少个字符串,可以用dfs做,也可以对自动机拓扑一下(其实就相当于把len从小到大排序,因为len小的拓扑序也会小),然后倒着求一下(相当于DAG上的DP),然后dfs去找第K大的就好了。
2、找相同串的第K大:除了要预处理出上面的东西,还要预处理出所有状态right集合的大小(每个串在原串中出现多少次),这个会影响上面的要求的值,然后在做dfs的时候同时处理一下就好了。
原题TJOI2015弦论

4、求最小循环表示

最小循环表示就是这个串的所有循环串中的字典序最小的串。
把原串复制一遍到后面,然后建立后缀自动机,每次当前连出走字典序最小的点,一直走到长度为|S|为止。

5、找回文串

构造原串的后缀自动机,求出每个节点right集合的rmax,然后把反串放到后缀自动机上面运行,如果当前的匹配串在原串中的范围[l…r]覆盖了当前节点的rmax,那么[l…rmax]就是一个回文串。

6、Trie上建SAM

看起来很高级的样子,其实就是每个节点的last就是trie上的父节点。为什么要在trie上建呢?
比如说要把很多个串同时建立后缀自动机,那么有两种方法:
方法一:把所有的字符串都用一个与众不同的字符隔起来,然后建立后缀自动机。
方法二:把所有的字符串放到一个trie上,然后在trie上建立后缀自动机。(其实这个好像也叫广义后缀自动机)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值