SAM(后缀自动机)的题型总结

模板

string s;
int fa[maxn],ch[maxn][26];
int len[maxn],last=1,tot=1;
struct SAM{
   
    void Insert(int c)
    {
   
        int p=last,cur=++tot;
     	len[cur]=len[last]+1;
	    last=cur;
	    for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;//跳fail
	    if(!p) fa[cur]=1;//情况1
	    else{
   
            int q=ch[p][c];
		    if(len[p]+1==len[q]) fa[cur]=q;//情况2
		    else{
   //情况3
		        int clone=++tot;
			    len[clone]=len[p]+1;
			    for(int i=0;i<26;i++) ch[clone][i]=ch[q][i];
			    fa[clone]=fa[q];
			    fa[q]=fa[cur]=clone;
			    for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
		    }
        }
    }
};

此图为aababa的后缀自动机,包含三种情况。做题时可以直接用这个例子。

下面所有题型都以这个字符串举例

常见后缀自动机题型分类

常见用法:
1、判断是否为子串:

    bool Search(string s)
    {
   
        int p=1;
        int num=0;
        int c=s[num]-'a';
        int cnt=0;
        while(ch[p][c]){
   
            p=ch[p][c];
            cnt++;
            c=s[++num]-'a';
        }
        if(cnt==s.length()) return true;
        else return false;
    }

2、求不同字串个数

    void cal()
    {
   
        for(int i=1;i<=tot;i++){
   
            sum+=len[i]-len[fa[i]];
        }
    }

3、求所有子串长度之和
等差数列求和

    void cal()
    {
   
        for(int i=1;i<=tot;i++){
   
            int n=len[i]-len[fa[i]];
            ans+=n*(len[fa[i]]+1)+n*(n-1)/2;
        }
    }

4、字典序第k大子串
本质来说就是回文自动机有多条路径,然后去选择第k大的路径
(1)、如果位置不同算作多个
先去求每个节点中的子字符串在总字符串中出现的次数,然后从最长串开始,依次去累加经过这个节点的字符串的出现次数。例如9节点,包含的字符串在总字符串中出现2次,因为他连着6号节点,也就相当于把跟着6号节点接下来的所有路径全部包含,所以只需要累加6号节点的sum值即可。

    void Insert() siz[cur]=1;
    void cal()
    {
   //siz数组为在字符串中出现的次数,sum为经过这个节点字符串的总数
        for(int i=1;i<=tot;i++) c[len[i]]++;
        for(int i=1;i<=tot;i++) c[i]+=c[i-1];
	    for(int i=1;i<=tot;i++) x[c[len[i]]--]=i;
	    for(int i=tot;i>=1;i--) siz[fa[x[i]]]+=siz[x[i]];
	    for(int i=1;i<=tot;i++){
   
            sum[i]=siz[i];
	    }
	    siz[1]=sum[1]=0;
	    for(int i=tot;i>=1;i--){
   
            for(int j=0;j<26;j++){
   
                if(ch[x[i]][j]) sum[x[i]]+=sum[ch[x[i]][j]];
            }
	    }
    }
    void dfs(int x,int k)
    {
   
        if(k<=siz[x]) return;
        k-=siz[x];
        for(int i=0;i<26;i++){
   
            int y=ch[x][i];
            if(!y) continue;
            if(k>sum[y]){
   
                k-=sum[y];
                continue;
            }
      
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值