后缀自动机+DP BZOJ 3238 差异

本文介绍了一种求解字符串中所有后缀最长公共前缀(LCP)长度之和的方法,利用反转字符串并构建后缀自动机(SAM)来简化问题。通过两个关键推论,实现了高效计算,并给出了详细的C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析:
其实题目就是要求任意两个后缀T[i]和T[j] (i< j) 的 LCP长度之和。
首先对输入的字符串反转后建立SAM。
令 一个节点的Max表示它代表的最长子串。
推论1: 原串中的两个后缀的LCP长度等于后缀树上两个节点的LCA 的 Max.
基于这个推论,只需要枚举LCA,讨论子树与子树之间的组合数问题。
虽然理论上要建立后缀树,但代码里并不用真的建一棵树再DFS。
具体做法如下:
推论2: Max更大的节点一定在后缀树中深度更大
根据这个推论,我们可以按照Max从小到大进行排序,得到一个序列dfn[]就是深度从浅到深的节点编号,可以进行DP。
考虑到1<=Max<=n, 代码中用的是O(n)的基数排序。
注意: DP中累加size的时候,建立的辅助节点nq没有算在其中。

代码:

/**************************************************************
    Problem: 3238
    User: spark
    Language: C++
    Result: Accepted
    Time:3072 ms
    Memory:244452 kb
****************************************************************/

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<cstring>
#define LL long long
using namespace std;
const int maxn=1000000+5;

int tot=1,last=1,n,dfn[maxn],cnt[maxn];
char s[maxn];

struct node{
    int Next[26];
    int Max,size,pre,sons;
}T[maxn<<1];

template <typename T>
inline void _read(T &x){
    char ch=getchar(); bool mark=false;
    for(;!isdigit(ch);ch=getchar())if(ch=='-')mark=true;
    for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    if(mark)x=-x;
}

void Insert(char x){
    int id= x-'a';
    int np= ++tot,cur=last;
    T[np].Max=T[last].Max+1; T[np].size=T[np].sons=1;
    while(cur){
        if(!T[cur].Next[id])  T[cur].Next[id]= np;
        else {
            int v= T[cur].Next[id];
            if(T[v].Max==T[cur].Max+1) T[np].pre=v;
            else{
                int nq= ++tot;
                memcpy(T[nq].Next,T[v].Next,sizeof(T[v].Next));
                T[nq].Max=T[cur].Max+1;
                T[nq].pre=T[v].pre; T[v].pre= nq; 
                T[np].pre= nq; 
                for(int i= cur;T[i].Next[id]==v;i=T[i].pre)
                    T[i].Next[id]=nq;
            } 
            break;
        }
        cur=T[cur].pre;
    }
    if(!T[np].pre)T[np].pre=1;
    last=np;
}

LL Count(){
    int i;
    LL ans=0;
    for(i=1;i<=tot;i++) cnt[T[i].Max]++;
    for(i=1;i<=n;i++)cnt[i]+=cnt[i-1];
    for(i=1;i<=tot;i++)dfn[cnt[T[i].Max]--]= i;
    // cout<<"dfn: ";for(i=1;i<=tot;i++)cout<<dfn[i]<<" ";cout<<endl;
    for(i=tot;i>0;i--)T[T[dfn[i]].pre].size+=T[dfn[i]].size;
    for(i=1;i<=tot;i++){
        int fa= T[i].pre;
        ans+= 1ll*T[fa].sons*T[fa].Max*T[i].size;
        T[fa].sons+= T[i].size;
    }
    return ans;
}

int main(){
    int i,j;
    scanf("%s",s+1);
    n= strlen(s+1); 
    for(i=n;i>0;i--)Insert(s[i]);
    LL ans=0;
    for(i=1;i<=n;i++) ans+= (1ll*i*(n-1));
    cout<<ans-2*Count()<<endl;
    return 0;
}
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define Marx_is_dead true
#define ll long long
using namespace std;
template <typename T>
inline void _read(T& x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9'){if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
int n,m;
int last=1,root=1,tot=1;
struct node{
    int son[26];
    int maxn,size,par,temp;
};
node Auto[2000005];
char s[1000005];
void insert(char ch){
    int i,j,k,nq,p,q,t=ch-'a';
    int np=++tot;
    Auto[np].size=1;
    Auto[np].temp=1;
    Auto[np].maxn=Auto[last].maxn+1;
    for(p=last;!Auto[p].son[t];p=Auto[p].par)Auto[p].son[t]=np;
    if(!p)Auto[np].par=root;
    else {
        q=Auto[p].son[t];
        if(Auto[q].maxn!=Auto[p].maxn+1){
            nq=++tot;
            memcpy(Auto[nq].son,Auto[q].son,sizeof(Auto[q].son));
            //Auto[nq]=Auto[q];
            Auto[nq].maxn=Auto[p].maxn+1;
            Auto[nq].par=Auto[q].par;
            Auto[q].par=nq;
            Auto[np].par=nq;
            for(;Auto[p].son[t]==q;p=Auto[p].par)Auto[p].son[t]=nq;
        }
        else Auto[np].par=q;
    }
    if(Auto[np].par==0)Auto[np].par=root;
    last=np;
}
int cnt[1000005];
int dfn[1000005];
int main(){
    int i,j,len;
    scanf("%s",s+1);
    len=strlen(s+1);
    for(i=len;i;i--){
        insert(s[i]);
    }
    ll ans=1ll*(len+1)*(len-1)*len/2;
    for(i=1;i<=tot;i++)cnt[Auto[i].maxn]++;
    for(i=1;i<=len;i++)cnt[i]+=cnt[i-1];
    for(i=1;i<=tot;i++)dfn[cnt[Auto[i].maxn]--]=i;
    for(i=tot;i;i--){
        Auto[Auto[dfn[i]].par].size+=Auto[dfn[i]].size;
    }
    ll dec=0;
    for(i=1;i<=tot;i++){
        int fa=Auto[i].par;
        dec+=1ll*Auto[fa].temp*Auto[i].size*Auto[fa].maxn;
        Auto[fa].temp+=Auto[i].size;
    }
    cout<<ans-2*dec;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值