[BZOJ3238][Ahoi2013]差异(后缀自动机||后缀数组)

=== ===

这里放传送门

=== ===

题解

这题有两种做法:后缀自动机和后缀数组。

后缀自动机的做法就是Parent树性质的应用,也就是如果把它的fa指针摘出来连边搞成一棵树就像AC自动机的fail树那样,那么可以发现每一个串在Parent树上都对应了一个节点,而两个串的最长公共后缀就是它们在Parent树上的最近公共祖先。原理也很简单,因为一个节点在Parent树上的所有祖先都代表它的后缀,而离它越近的祖先代表的串就越长,那么最长公共后缀肯定就是最近的公共祖先了。这个题实际上就是要求后缀们两两LCP长度之和,那么把串反过来就变成了前缀们的最长公共后缀长度之和。对反串建立后缀自动机然后把Parent树建出来,然后dfs一遍求出每个点对于LCA的贡献即可。

后缀数组的做法就不需要把串反置什么的了,因为求出来的height数组本身就是后缀的LCP。于是就变成了求后缀之间两两LCP之和,这涉及到height数组的区间最小值,可以维护一个单调上升的栈,计算每个点作为区间最小值的贡献,这是一个比较经典的问题。

代码

后缀自动机

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int len,cnt,point[1000010],a[2000010],nxt[2000010],deep[1000010],tot,w[2000010];
bool mark[1000010];
long long ans,v[1000010],sum;
char s[500010];
struct Node{
    Node *ch[30],*fa;
    int step,num;
    Node();
}*null,*Root,P[1000010],*last,*p,*q,*np,*nq;
Node::Node(){
    for (int i=0;i<=26;i++) ch[i]=null;
    fa=null;step=0;
}
Node *New(){P[++cnt]=Node();P[cnt].num=cnt;return P+cnt;}
void add(int x,int y,int val){
    tot++;a[tot]=y;nxt[tot]=point[x];w[tot]=val;point[x]=tot;
}
void insert(int c){
    p=last;last=np=New();
    np->step=p->step+1;
    mark[np->num]=true;
    while (p->ch[c]==null&&p!=null){
        p->ch[c]=np;p=p->fa;
    }
    if (p==null){np->fa=Root;return;}
    q=p->ch[c];
    if (q->step==p->step+1){np->fa=q;return;}
    nq=New();nq->step=p->step+1;
    memcpy(nq->ch,q->ch,sizeof(q->ch));
    nq->fa=q->fa;q->fa=np->fa=nq;
    while (p->ch[c]==q){p->ch[c]=nq;p=p->fa;}
}
long long search(int u,int fa,int from){
    long long c=mark[u];
    deep[u]=deep[fa]+from;
    for (int i=point[u];i!=0;i=nxt[i])
      if (a[i]!=fa){
          long long tmp=search(a[i],u,w[i]);
          v[u]+=c*tmp;c+=tmp;
      }
    return c;
}
int main()
{
    null=new Node;*null=Node();
    gets(s);len=strlen(s);
    last=Root=New();
    for (int i=0;i<len;i++)
      insert(s[len-i-1]-'a');
    for (int i=1;i<=cnt;i++){
        Node *v=P+i;
        int val=v->step-v->fa->step;
        add(v->num,v->fa->num,val);
        add(v->fa->num,v->num,val);
    }
    search(1,0,0);
    for (int i=1;i<=cnt;i++)
      ans+=(long long)deep[i]*v[i];
    sum=(long long)len*(len+1);
    sum=sum/2*(len-1);ans*=2;
    printf("%I64d\n",sum-ans);
    return 0;
}

后缀数组

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int len,m,p,*x,*y,SA[500010],rank[500010],height[500010],st[500010],L[500010];
int top,Xx[500010],Yy[500010],b[500010];
long long now,ans,sum;
char s[500010];
bool cmp(int i,int j,int l){
    return y[i]==y[j]&&((i+l>=len)?-1:y[i+l])==((j+l>=len)?-1:y[j+l]);
}
void get_SA(){
    m=200;x=Xx;y=Yy;
    for (int i=0;i<len;i++) ++b[x[i]=s[i]];
    for (int i=1;i<=m;i++) b[i]+=b[i-1];
    for (int i=len-1;i>=0;i--) SA[--b[x[i]]]=i;
    for (int k=1;k<=len;k<<=1){
        p=0;
        for (int i=len-k;i<len;i++) y[p++]=i;
        for (int i=0;i<len;i++)
          if (SA[i]>=k) y[p++]=SA[i]-k;
        for (int i=0;i<=m;i++) b[i]=0;
        for (int i=0;i<len;i++) ++b[x[i]];
        for (int i=1;i<=m;i++) b[i]+=b[i-1];
        for (int i=len-1;i>=0;i--) SA[--b[x[y[i]]]]=y[i];
        swap(x,y);p=1;x[SA[0]]=0;
        for (int i=1;i<len;i++)
          x[SA[i]]=cmp(SA[i-1],SA[i],k)?p-1:p++;
        if (p>=len) break;m=p;
    }
    p=0;
    for (int i=0;i<len;i++) rank[SA[i]]=i;
    for (int i=0;i<len;i++){
        if (rank[i]==0) continue;
        int j=SA[rank[i]-1];
        while (i+p<len&&j+p<len&&s[i+p]==s[j+p]) ++p;
        height[rank[i]]=p;
        if (p!=0) --p;
    }
}
long long calc(int i){return (long long)height[i]*(i-L[i]);}
int main()
{
    gets(s);len=strlen(s);
    get_SA();
    for (int i=1;i<len;i++){
        L[i]=i-1;
        while (top!=0&&height[st[top]]>=height[i]){
            now-=calc(st[top]);
            L[i]=L[st[top]];--top;
        }
        st[++top]=i;now+=calc(i);
        ans+=now;
    }
    sum=(long long)len*(len+1);
    sum/=2;sum=(long long)(len-1)*sum;
    printf("%I64d\n",sum-2*ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值