bzoj3238/洛谷4284 [Ahoi2013]差异
题意:设 Ti T i 表示从i开始的后缀。求 ∑1≤i≤j≤nlen(Ti)+len(Tj)−lcp(Ti,Tj) ∑ 1 ≤ i ≤ j ≤ n l e n ( T i ) + l e n ( T j ) − l c p ( T i , T j )
方法:显然
∑1≤i≤j≤nlen(Ti)+len(Tj)
∑
1
≤
i
≤
j
≤
n
l
e
n
(
T
i
)
+
l
e
n
(
T
j
)
可以
O(1)
O
(
1
)
求出,那么问题就在于怎么求
∑1≤i≤j≤nlcs(Ti,Tj)
∑
1
≤
i
≤
j
≤
n
l
c
s
(
T
i
,
T
j
)
也就是求每两个后缀的公共前缀,解决这个问题可以用后缀数组,也可以用后缀自动机
上面的是后缀自动机的..下面是后缀数组
可见…后缀自动机啊..内存真的大…速度真的快,好像还比后缀数组短【捂脸】
方法一:后缀数组
通过后缀数组,我们可以得到height。
题目要求任意两个串的 lcp,我们知道两个串的 lcp,就是他们之间height的最小值
所以就转化成了求序列所有区间最小值*2
然后就可以通过单调栈,找到左边最远做到哪里,右边最远做到哪里,影响就是
height[i]∗(i−l[i]+1)∗(r[i]−i+1)
h
e
i
g
h
t
[
i
]
∗
(
i
−
l
[
i
]
+
1
)
∗
(
r
[
i
]
−
i
+
1
)
#include <cstdio>
#include <cstring>
#define N 550000
char str[N];
int n,a[N],r[N],l[N],count[N],rank1[N],rank[2*N],height[N],tmp[N],sa[N],stack[N];
long long ans;
int main() {
scanf("%s",str+1);n=strlen(str+1);
memset(count,0,sizeof(count));
for(int i=1;i<=n;i++) a[i]=str[i]-'a'+1;
for(int i=1;i<=n;i++) count[a[i]]=1;
for(int i=1;i<=26;i++) count[i]+=count[i-1];
for(int i=1;i<=n;i++) rank[i]=count[a[i]];
int k=0;for(int len=1;k!=n;len<<=1){
memset(count,0,sizeof(count));
for(int i=1;i<=n;i++) count[rank[i+len]]++;
for(int i=1;i<=n;i++) count[i]+=count[i-1];
for(int i=n;i>=1;i--) tmp[count[rank[i+len]]--]=i;
memset(count,0,sizeof(count));
for(int i=1;i<=n;i++) count[rank[tmp[i]]]++;
for(int i=1;i<=n;i++) count[i]+=count[i-1];
for(int i=n;i>=1;i--) sa[count[rank[tmp[i]]]--]=tmp[i];
memcpy(rank1,rank,sizeof(rank1));
k=1;rank[sa[1]]=1;
for(int i=2;i<=n;i++){
if(rank1[sa[i]]!=rank1[sa[i-1]] || rank1[sa[i]+len]!=rank1[sa[i-1]+len]) k++;
rank[sa[i]]=k;
}
}k=0;
for(int i=1;i<=n;i++){
if(rank[i]==1) k=0;
else{
if(--k<0) k=0;
while(a[i+k]==a[sa[rank[i]-1]+k]) k++;
}height[rank[i]]=k;
}
ans=(long long)n*(n+1)*(n-1)/2;
int top=0;
for(int i=2;i<=n;i++){
while(top && height[i]<height[stack[top]]) r[stack[top--]]=i-1;
stack[++top]=i;
}while(top) r[stack[top--]]=n;
top=0;
for(int i=n;i>=1;i--){
while(top && height[i]<=height[stack[top]]) l[stack[top--]]=i+1;
stack[++top]=i;
}while(top) l[stack[top--]]=2;
for(int i=2;i<=n;i++) ans-=(long long)2*height[i]*(r[i]-i+1)*(i-l[i]+1);
printf("%lld",ans);
return 0;
}
方法二:后缀自动机
我们可以构造出后缀树,然后两个串的最长公共前缀就是他们的
lca
l
c
a
,而且每个点(c出叶节点)都会有当
lca
l
c
a
的机会。所有我们考虑每个点为
lca
l
c
a
的情况
,必然是在这个点下面选两个点,因此就是
len⋅C2size[p]
l
e
n
⋅
C
s
i
z
e
[
p
]
2
不过因为每个点都回当lca,所有我们就只做着一段即可(p——fa[p]),剩下的会在fa[p]处处理,不然会有重复
至于后缀树的建立,反串的parent tree才是原串的后缀树
#include <cstdio>
#include <cstring>
#define N 500010
#define ll long long
int cnt=0,last,root,n,mx[N<<1],size[N<<1],son[N<<1][26],fa[N<<1],c[N<<1],a[N<<1],sum[N<<1];
char str[N];
ll ans=0;
inline void ins(int ch){
int p=last,np=++cnt;last=np;mx[np]=mx[p]+1;size[np]=1;
while(p && !son[p][ch]) son[p][ch]=np,p=fa[p];
if(!p) fa[np]=root;
else{
int q=son[p][ch];
if(mx[q]==mx[p]+1) fa[np]=q;
else{
int nq=++cnt;mx[nq]=mx[p]+1;
memcpy(son[nq],son[q],sizeof(son[nq]));
fa[nq]=fa[q];fa[np]=fa[q]=nq;
while(son[p][ch]==q) son[p][ch]=nq,p=fa[p];
}
}
}
int main(){
scanf("%s",str+1);last=root=++cnt;n=strlen(str+1);
for(int i=n;i>=1;i--) ins(str[i]-'a');
for(int i=1;i<=cnt;i++) c[mx[i]]++;
for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
for(int i=cnt;i>=1;i--) a[c[mx[i]]--]=i;
for(int i=cnt;i>=1;i--){
int p=a[i];size[fa[p]]+=size[p];
ans+=(ll)(mx[p]-mx[fa[p]])*size[p]*(size[p]-1);
}printf("%lld\n",(ll)n*(n+1)*(n-1)/2-ans);
return 0;
}