洛谷P2408 不同子串個數(後綴自動機)

題目

長爲n(n<=1e5)的字符串,求其本質不同的子串的個數

思路來源

https://www.luogu.com.cn/blog/user7035/solution-p2408 逆拓撲序

https://www.luogu.com.cn/blog/tanrui-2960967961/solution-p2408 動態維護

https://blog.csdn.net/Code92007/article/details/82820460 後綴數組

題解

之前學SA的時候,做過這麼個題,補一下SAM的做法

可以通過自動機轉移和節點維護的字符串兩個思路做,

①自動機轉移,每個原串的後綴都能到終點被識別,則子串是後綴的一段前綴,對應了自動機上的一段路徑

由於SAM是一個DAG,即統計DAG上不同路徑的條數,這個按逆拓撲序往回更新一下即可

對於節點u,u如果通過字母c轉移到了後繼v,則dp[u]+=dp[v]+1(加1是隻考慮字母c的這一條路徑)

可以通過dfs一下根節點向下記憶化搜索實現,也可以逆拓撲序往回更新實現,這裏採用後者

②每個子串僅出現在一個節點裏,節點a裏出現的子串的長度爲[minlen(a),len(a)],

對應貢獻了len(a)-minlen(a)+1個子串,動態統計即可

代碼1

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
struct SAM{
    struct NODE
    {
        int ch[26];
        int len,fa,sz;
        NODE(){memset(ch,0,sizeof(ch));len=sz=0;}
    }dian[N<<1];
    int las=1,tot=1,len;//rt爲1 代表空串
    char s[N];
    void add(int c)
    {
        int p=las;int np=las=++tot;
        dian[np].sz=1;
        dian[np].len=dian[p].len+1;
        for(;p&&!dian[p].ch[c];p=dian[p].fa)dian[p].ch[c]=np;
        if(!p)dian[np].fa=1;//以上爲case 1
        else
        {
            int q=dian[p].ch[c];
            if(dian[q].len==dian[p].len+1)dian[np].fa=q;//以上爲case 2
            else
            {
                int nq=++tot;dian[nq]=dian[q];
                dian[nq].sz=0;
                dian[nq].len=dian[p].len+1;
                dian[q].fa=dian[np].fa=nq;
                for(;p&&dian[p].ch[c]==q;p=dian[p].fa)dian[p].ch[c]=nq;//以上爲case 3
            }
        }
    }
    void init()
    {
        scanf("%d%s",&len,s);
        for(int i=0;i<len;i++)add(s[i]-'a');
    }
}sam;
int c[N<<1],a[N<<1];//c用於基數排序 a用於記錄點號
ll ans[N<<1];
int main()
{
    sam.init();
    for(int i=1;i<=sam.tot;++i)c[sam.dian[i].len]++;
    for(int i=1;i<=sam.tot;++i)c[i]+=c[i-1];
    for(int i=1;i<=sam.tot;++i)a[c[sam.dian[i].len]--]=i;
    for(int i=sam.tot;i>=1;--i){//從DAG拓撲序底層開始考慮其後繼 逆拓撲序
        int now=a[i];
        for(int j=0;j<26;++j){
            int nex=sam.dian[now].ch[j];
            if(nex){
                ans[now]+=ans[nex]+1;
            }
        }
    }
    printf("%lld\n",ans[1]);
    return 0;
}

代碼2

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
ll res;
struct SAM{
    struct NODE
    {
        int ch[26];
        int len,fa,sz;
        NODE(){memset(ch,0,sizeof(ch));len=sz=0;}
    }dian[N<<1];
    int las=1,tot=1,len;//rt爲1 代表空串
    char s[N];
    void add(int c)
    {
        int p=las;int np=las=++tot;
        dian[np].sz=1;
        dian[np].len=dian[p].len+1;
        for(;p&&!dian[p].ch[c];p=dian[p].fa)dian[p].ch[c]=np;
        if(!p)dian[np].fa=1;//以上爲case 1
        else
        {
            int q=dian[p].ch[c];
            if(dian[q].len==dian[p].len+1)dian[np].fa=q;//以上爲case 2
            else
            {
                int nq=++tot;dian[nq]=dian[q];
                dian[nq].sz=0;
                dian[nq].len=dian[p].len+1;
                dian[q].fa=dian[np].fa=nq;
                for(;p&&dian[p].ch[c]==q;p=dian[p].fa)dian[p].ch[c]=nq;//以上爲case 3
            }
        }
        res+=dian[np].len-dian[dian[np].fa].len;//[minlen(a),len(a)]
    }
    void init()
    {
        scanf("%d%s",&len,s);
        for(int i=0;i<len;i++)add(s[i]-'a');
    }
}sam;
int main()
{
    sam.init();
    printf("%lld\n",res);
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章