P3804 【模板】后缀自动机(SAM)

题目描述

给定一个只包含小写字母的字符串 \(S\)

请你求出 \(S\) 的所有出现次数不为 \(1\) 的子串的出现次数乘上该子串长度的最大值。

输入格式

一行一个仅包含小写字母的字符串 \(S\)

输出格式

一个整数,为所求答案。

输入输出样例 #1

输入 #1

abab

输出 #1

4

说明/提示

对于 \(10 \%\) 的数据,\(\lvert S \rvert \le 1000\)
对于 $100% $的数据,\(1 \le \lvert S \rvert \le {10}^6\)

  • 2023.7.30:添加一组 hack 数据。

题解

#include <bits/stdc++.h>
using namespace std;
const int N=2e6+10;
typedef long long ll;
int n;
char s[N];
vector<int> e[N];//邻接表
ll cnt[N],ans;
int tot=1,np=1;

//fa链接边终点,ch转移边终点,len最长串长度
int fa[N],ch[N][26],len[N];

void extend(int c)
{
    //p回跳指针, np新点, q链接点, nq新链接点
    int p=np; np=++tot;//p指向旧点, np是新点
    len[np]=len[p]+1; cnt[np]=1;//子串出现次数 
    //p沿链接边回跳,从旧点向新点建转移边
    for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
    //1.如果c是新字符,从新点向根节点建链接边
    if(p==0)fa[np]=1;
    else
    {//如果c是旧字符
        int q=ch[p][c];//q是链接点
        //2.若链接点合法,从新点向q建链接边
        if(len[q]==len[p]+1)fa[np]=q;
        //3.若链接点不合法,则裂开q点,重建两类边
        else
        {
            int nq=++tot;//nq是新链接点
            len[nq]=len[p]+1;
            //重建nq,q,np的链接边
            fa[nq]=fa[q]; fa[q]=nq; fa[np]=nq;
            //指向q的转移边改为指向nq
            for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
            //从q发出的转移边复制给nq
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
        }
    }
}
void dfs(int u)
{ 
    for(auto v : e[u])
    {
        dfs(v);
        cnt[u]+=cnt[v];
    }
    if(cnt[u]>1)ans=max(ans,cnt[u]*len[u]);
}

void solve()
{
    cin>>s;
    for(int i=0;s[i];i++)extend(s[i]-'a');
    for(int i=2;i<=tot;i++)e[fa[i]].push_back(i);
    dfs(1);
    cout<<ans<<endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    // cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}
posted @ 2025-11-28 22:22  屈臣  阅读(7)  评论(0)    收藏  举报