洛谷P5546公共串的SAM解法代码(含二分哈希)

写作原因:

我看网上大部分都是二分哈希的做法但却没有后缀自动机SAM的解法代码,因此提供两个SAM的解法,两个解法我都不详细介绍,会提供原文章地址和我个人的代码。个人认为第二种解法在时间和空间复杂度上都更优,也更好理解(前提你熟悉两个字符串情况下的SAM解法)。

SAM解法的优点:

时间复杂度是O(N)的,在数据量大的时候会快很多,可以通过数据更强的SPOJ.com - Problem LCS2

题意:

输入k个字符串,求这k个字符串间的最长公共子串的长度。

解法一:

原地址:后缀自动机 (SAM) - OI Wiki (oi-wiki.org)

我的AC代码:
//
// Created by 15411 on 2024/8/30.
//
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
using namespace std;
const int N=2e5+7;
int k,ans;//字符串数量
string s,t;
namespace SAM{
    struct node{
        int link,len;
        int son[27],vst[10];//记录十种&的可能
    }t[N<<1];
    int sz,last,kk;
    bitset<N<<1>vst;
    void newNode(int length){
        t[++sz].len=length;
        t[sz].link=-1;
        memset(t[sz].son,0,sizeof(t[sz].son));
        memset(t[sz].vst,0,sizeof(t[sz].vst));
    }
    void init(){
        for(int i=0;i<=sz;i++)vst[i]=false;
        sz=-1;last=0;ans=0;kk=-1;
        newNode(0);
    }
    void insert(int c){
        newNode(t[last].len+1);
        if(c<0){
            vst[sz]=true;
            t[sz].vst[++kk]=true;
            c=26;//&
        }
        int p=last,cur=sz;
        while(p!=-1&&!t[p].son[c])
            t[p].son[c]=cur,p=t[p].link;
        if(p==-1)
            t[cur].link=0;
        else{
            int q=t[p].son[c];
            if(t[q].len==t[p].len+1)
                t[cur].link=q;
            else{
                newNode(t[p].len+1);
                int nq=sz;
                memcpy(t[nq].son,t[q].son,sizeof(t[q].son));
                memcpy(t[nq].vst,t[q].vst,sizeof(t[q].vst));
                if(vst[q])vst[nq]=true;
                t[nq].link=t[q].link;
                t[cur].link=t[q].link=nq;
                while(p!=-1&&t[p].son[c]==q)
                    t[p].son[c]=nq,p=t[p].link;
            }
        }
        last=cur;
    }
    vector<int>G[N<<1];
    void dfs(int now){
        for(auto next:G[now]){
            dfs(next);
            for(int i=0;i<k;i++){
                if(t[next].vst[i])
                    t[now].vst[i]=true;
            }
        }
    }
    void Find(int now){//查询当前点的子中有几个&
        vst[now]=true;
        for(int i=0;i<27;i++){
            if(!t[now].son[i])continue;
            int cd=t[now].son[i];
            if(!vst[cd])Find(cd);

            for(int j=0;j<k;j++) {
                if(t[cd].vst[j])
                    t[now].vst[j]=true;
            }
        }
        int cnt=0;
        for(int i=0;i<k;i++)if(t[now].vst[i])cnt++;
        if(cnt==k)
            ans=max(ans,t[now].len);
    }
    void query(){
        for(int i=0;i<=sz;i++)G[i].clear();
        for(int i=1;i<=sz;i++)G[t[i].link].push_back(i);
        dfs(0);
        Find(0);
    }
}
void solve() {
    cin>>k;
    s.clear();
    for(int i=1;i<=k;i++){
        cin>>t;
        s+=t;
        s+='&';
    }

    int len=(int)s.size();
    SAM::init();
    for(int i=0;i<len;i++){
        SAM::insert(s[i]-'a');
    }
    SAM::query();
    cout<<ans<<endl;
}
signed main(){
    ios::sync_with_stdio(false);//cin.tie(0);cout.tie(0);
    solve();
    return 0;
}

解法二

原地址:Longest Common Substring II SPOJ - LCS2 (后缀自动机) - Jiaaaaaaaqi - 博客园 (cnblogs.com)
我的AC代码:
//
// Created by 15411 on 2024/8/30.
//
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
using namespace std;
const int N=2e5+20;
const int INF=1e9+7;
int k,LCM;//字符串数量
string s;
char t[N];
namespace SAM{
    struct node{
        int link,len;
        int cnt,lcm_last,lcm_now;//之前串的每个点的LCM取min,当前串每个点的LCM取max
        int son[26];
    }t[N<<1];
    int sz,last;
    void newNode(int length){
        t[++sz].len=length;
        t[sz].link=-1;t[sz].cnt=1;t[sz].lcm_last=INF;
        memset(t[sz].son,0,sizeof(t[sz].son));
    }
    void init(){
        sz=-1;last=0;
        newNode(0);
    }
    void insert(int c){
        newNode(t[last].len+1);
        int p=last,cur=sz;
        while(p!=-1&&!t[p].son[c])
            t[p].son[c]=cur,p=t[p].link;
        if(p==-1)
            t[cur].link=0;
        else{
            int q=t[p].son[c];
            if(t[q].len==t[p].len+1)
                t[cur].link=q;
            else{
                newNode(t[p].len+1);
                int nq=sz;
                memcpy(t[nq].son,t[q].son,sizeof(t[q].son));
                t[nq].link=t[q].link;
                t[cur].link=t[q].link=nq;
                while(p!=-1&&t[p].son[c]==q)
                    t[p].son[c]=nq,p=t[p].link;
            }
        }
        last=cur;
    }
    int ans[N];
    vector<int>G[N<<1];
    void dfs(int now){
        for(auto next:G[now]){
            dfs(next);
            t[now].lcm_now=max(t[now].lcm_now,t[next].lcm_now);
            t[now].lcm_now=min(t[now].len,t[now].lcm_now);//父节点虽然匹配上了,但要防止越界
        }
    }
    void init2(){
        for(int i=0;i<sz;i++)G[i].clear();
        for(int i=1;i<=sz;i++)G[t[i].link].push_back(i);
    }
    void query(char* s) {
        for (int i = 0; i <= sz; i++)t[i].lcm_now = 0;
        int now = 0, n = (int) strlen(s);
        for (int i = 0; i < n; i++) {
            if (t[now].son[s[i] - 'a']) {
                now = t[now].son[s[i] - 'a'];
                ans[i + 1] = ans[i] + 1;
            } else {
                while (now != -1 && !t[now].son[s[i] - 'a'])
                    now = t[now].link;
                if (now == -1) {
                    now = 0;
                    ans[i + 1] = 0;
                } else {
                    ans[i + 1] = t[now].len + 1;
                    now = t[now].son[s[i] - 'a'];
                }
            }
            t[now].lcm_now = max(t[now].lcm_now, ans[i + 1]);
        }
        dfs(0);
        for (int i = 0; i <= sz; i++) {
            if (!t[i].lcm_now)continue;
            t[i].cnt++;//记得统计这个点是不是被匹配了k次
            t[i].lcm_last = min(t[i].lcm_last, t[i].lcm_now);
        }
    }
    void query(){
        LCM=0;
        for(int i=0;i<=sz;i++){
            if(t[i].cnt==k)
                LCM=max(LCM,t[i].lcm_last);
        }
    }
}
void solve() {
    cin>>k>>s;
    int len=(int)s.size();
    SAM::init();
    for(int i=0;i<len;i++)SAM::insert(s[i]-'a');
    SAM::init2();
    for(int i=2;i<=k;i++){
        cin>>t;
        SAM::query(t);
    }
    SAM::query();
    if(k==1)
        LCM=len;
    cout<<LCM;
}
signed main(){
    ios::sync_with_stdio(false);//cin.tie(0);cout.tie(0);
    solve();
    return 0;
}

思路3:

二分长度+哈希验证。

我的二分哈希代码:

//
// Created by 15411 on 2024/8/31.
//
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
using namespace std;
const int N=2e4+7;
namespace Hash{
    const int base1=13331,MOD1=1e9+7;
    int P1[N],H1[N];
    void init(char* s,int len){
        P1[0]=1;H1[0]=s[0]-'a';
        for(int i=1;i<len;i++){
            H1[i]=(H1[i-1]*base1)%MOD1+(s[i]-'a');H1[i]%=MOD1;
            P1[i]=(P1[i-1]*base1)%MOD1;
        }
    }
    int get_hash(int L,int R){
        int ret=H1[R]-H1[L-1]*P1[R-L+1];
        ret=(ret%MOD1+MOD1)%MOD1;
        return ret;
    }
}
char s[11][N];
int k;
bool yz(int LEN){
    map<ll,bool>vst[2];
    ll len=(ll)strlen(s[1]);
    Hash::init(s[1],len);
    for(int L=0,R=L+LEN-1;R<len;R++,L++){
        int H_now=Hash::get_hash(L,R);
        vst[1&1][H_now]=true;
    }
    for(int i=2;i<=k;i++){
        len=(ll)strlen(s[i]);
        Hash::init(s[i],len);
        vst[i&1].clear();
        for(int L=0,R=L+LEN-1;R<len;R++,L++){
            int H_now=Hash::get_hash(L,R);
            if(vst[!(i&1)][H_now])
                vst[i&1][H_now]=true;
        }
    }
    if(!vst[k&1].empty())return true;
    return false;
}
void solve() {
    cin>>k;
    ll l=0,r=2e5+7;
    for(int i=1;i<=k;i++){
        cin>>s[i];
        r=min(r,(ll)strlen(s[i]));
    }
    while(l<r){
        ll mid=(l+r+1)>>1;
        if(yz(mid))
            l=mid;
        else
            r=mid-1;
    }
    cout<<l<<endl;
}

signed main(){
#ifdef ONLINE_JUDGE
#else
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);//cin.tie(0);cout.tie(0);
//    ll t;cin>>t;while(t--)
        solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东方延绪z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值