=== ===
这里放传送门
=== ===
题解
这题有两种做法:后缀自动机和后缀数组。
后缀自动机的做法就是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;
}