1.endpos
对于字符串
s
s
s,
s
s
s的子串
t
t
t,定义
e
n
d
p
o
s
(
t
)
endpos(t)
endpos(t)为一个集合,这个集合的元素为
t
t
t每次在
s
s
s中出现的结束位置。
例:
s
=
a
b
c
a
a
b
c
s=abcaabc
s=abcaabc,
e
n
d
p
o
s
(
a
)
=
{
1
,
4
,
5
}
,
e
n
d
p
o
s
(
b
c
)
=
{
3
,
7
}
endpos(a)=\{1,4,5\},endpos(bc)=\{3,7\}
endpos(a)={1,4,5},endpos(bc)={3,7}。
特别的,定义
e
n
d
p
o
s
(
空
串
)
=
{
0
,
1
,
2
,
.
.
.
,
n
−
1
}
endpos(空串)=\{0,1,2,...,n-1\}
endpos(空串)={0,1,2,...,n−1}。
2.等价类
如果
s
s
s的两个字符串
t
1
,
t
2
t1,t2
t1,t2满足
e
n
d
p
o
s
(
t
1
)
=
e
n
d
p
o
s
(
t
2
)
endpos(t1)=endpos(t2)
endpos(t1)=endpos(t2),则称
t
1
,
t
2
t1,t2
t1,t2属于同一个等价类。
设
s
s
s的字串
t
1
,
t
2
,
.
.
.
,
t
m
−
1
,
t
m
(
∀
i
≠
j
,
t
i
≠
t
j
)
t_1,t_2,...,t_{m-1},t_m(\forall i\neq j,t_i\neq t_j)
t1,t2,...,tm−1,tm(∀i=j,ti=tj)属于同一个等价类,且没有其它
s
s
s的子串也属于这个等价类,则将字符串集
t
1
,
t
2
,
.
.
.
,
t
m
−
1
,
t
m
t_1,t_2,...,t_{m-1},t_m
t1,t2,...,tm−1,tm称为
s
s
s的一个等价类。后缀自动机中的每个结点都是一个等价类。
后缀自动机的结点数
=
=
=等价类的数量(包括空串),将后缀自动机中结点
i
i
i表示的等价类称为等价类
i
i
i。
特别的,结点
1
1
1表示空串,并称为结点
1
1
1为起始节点。
3.引理
定义
s
u
f
(
x
,
y
)
suf(x,y)
suf(x,y)表示串
x
x
x是串
y
y
y的后缀。
引理:对于
s
s
s的字串
t
1
,
t
2
,
t
3
t_1,t_2,t_3
t1,t2,t3,满足
∣
t
1
∣
<
∣
t
2
∣
<
∣
t
3
∣
|t_1|<|t_2|<|t_3|
∣t1∣<∣t2∣<∣t3∣且
s
u
f
(
t
1
,
t
2
)
,
s
u
f
(
t
2
,
t
3
)
,
e
n
d
p
o
s
(
t
1
)
=
e
n
d
p
o
s
(
t
3
)
suf(t_1,t_2),suf(t_2,t_3),endpos(t_1)=endpos(t_3)
suf(t1,t2),suf(t2,t3),endpos(t1)=endpos(t3),则
e
n
d
p
o
s
(
t
2
)
=
e
n
d
p
o
s
(
t
1
)
=
e
n
d
p
o
s
(
t
3
)
endpos(t_2)=endpos(t_1)=endpos(t_3)
endpos(t2)=endpos(t1)=endpos(t3)。
证:显然
e
n
d
p
o
s
(
t
3
)
⊂
e
n
d
p
o
s
(
t
2
)
⊂
e
n
d
p
o
s
(
t
1
)
endpos(t_3)\subset endpos(t_2)\subset endpos(t_1)
endpos(t3)⊂endpos(t2)⊂endpos(t1),故若
e
n
d
p
o
s
(
t
1
)
=
e
n
d
p
o
s
(
t
3
)
endpos(t_1)=endpos(t_3)
endpos(t1)=endpos(t3)必有结论成立。
推论:若
t
1
,
t
2
,
.
.
.
,
t
m
−
1
,
t
m
t_1,t_2,...,t_{m-1},t_m
t1,t2,...,tm−1,tm为
s
s
s的一个等价类,且
∣
t
1
∣
<
∣
t
2
∣
<
.
.
.
<
∣
t
m
−
1
∣
<
∣
t
m
∣
|t_1|<|t_2|<...<|t_{m-1}|<|t_m|
∣t1∣<∣t2∣<...<∣tm−1∣<∣tm∣),则必有
∣
t
i
∣
−
∣
t
i
−
1
∣
=
1
(
1
<
i
≤
m
)
,
s
u
f
(
t
i
−
1
,
t
i
)
|t_i|-|t_{i-1}|=1(1<i\le m),suf(t_{i-1},t_i)
∣ti∣−∣ti−1∣=1(1<i≤m),suf(ti−1,ti)。
4.link
设等价类
i
i
i的字串集为
t
1
,
t
2
,
.
.
.
,
t
m
t_1,t_2,...,t_m
t1,t2,...,tm,设字串
t
t
t包含于等价类
j
j
j中,且满足
∣
t
∣
=
∣
t
1
∣
−
1
,
s
u
f
(
t
,
t
1
)
|t|=|t_1|-1,suf(t,t_1)
∣t∣=∣t1∣−1,suf(t,t1),则定义
l
i
n
k
(
i
)
=
j
link(i)=j
link(i)=j。
性质:当
l
i
n
k
(
i
)
=
j
link(i)=j
link(i)=j,有
e
n
d
p
o
s
(
t
1
)
⊊
e
n
d
p
o
s
(
t
)
endpos(t_1)\subsetneq endpos(t)
endpos(t1)⊊endpos(t),所有的
l
i
n
k
link
link构成一颗以起始节点
1
1
1为根的树。
5.nex
引理:设 t 1 , t 2 , . . . , t m t_1,t_2,...,t_m t1,t2,...,tm为一个等价类,则对于任意的字符 c c c,字串集 t 1 + c , t 2 + c , . . . , t m + c t_1+c,t_2+c,...,t_m+c t1+c,t2+c,...,tm+c仍然属于同一个等价类(这个等价类可以是一个已经存在的等价类,也可以是一个新的等价类)。
若等价类
i
i
i中的所有字符后面都接上一个字符
c
c
c后,新的字串集属于等价类
j
j
j,则称
n
e
x
(
i
,
c
)
=
j
nex(i,c)=j
nex(i,c)=j(若
j
j
j不存在,则
n
e
x
(
i
,
c
)
=
n
u
l
l
nex(i,c)=null
nex(i,c)=null)。
n
e
x
nex
nex称为后缀自动机的转移函数(从某个等价类转移到另一个等价类)。
6.len
定义
l
e
n
(
i
)
len(i)
len(i)表示等价类
i
i
i的字串集中长度最大的字符串的长度。其最小长度为
l
e
n
(
l
i
n
k
(
i
)
)
+
1
len(link(i))+1
len(link(i))+1。
性质:
l
e
n
(
i
)
−
l
e
n
(
l
i
n
k
(
i
)
)
len(i)-len(link(i))
len(i)−len(link(i))为等价类
i
i
i中的字符串个数。
7.后缀自动机的构建
令字符串
s
=
s
1
s
2
s
3
.
.
.
s
n
s=s_1s_2s_3...s_n
s=s1s2s3...sn,令
p
r
e
(
x
)
=
s
1
s
2
.
.
.
s
x
(
1
≤
x
≤
n
)
pre(x)=s_1s_2...s_x(1\le x\le n)
pre(x)=s1s2...sx(1≤x≤n),令
p
r
e
(
0
)
pre(0)
pre(0)表示空串,现要构建
s
s
s的后缀自动机。
采用归纳法构造:
1.
p
r
e
(
0
)
pre(0)
pre(0)为空串,其后缀自动机只有一个起始结点(等价类),其
n
e
x
=
n
u
l
l
,
l
e
n
=
0
,
l
i
n
k
=
0
nex=null,len=0,link=0
nex=null,len=0,link=0。
2.假设当前已经构建了
p
r
e
(
i
−
1
)
pre(i-1)
pre(i−1)的后缀自动机,现构建
p
r
e
(
i
)
pre(i)
pre(i)的后缀自动机,这等价于在子串
s
i
,
s
i
−
1
s
i
,
s
i
−
2
s
i
−
1
s
i
,
.
.
.
s_i,s_{i-1}s_i,s_{i-2}s_{i-1}s_i,...
si,si−1si,si−2si−1si,...的
e
n
d
p
o
s
endpos
endpos中添加一个
i
i
i,并且必然会添加一个新的结点(因为
e
n
d
p
o
s
(
p
r
e
(
i
)
)
=
{
i
}
endpos(pre(i))=\{i\}
endpos(pre(i))={i})。
引理:设构建 p r e ( i ) pre(i) pre(i)的后缀自动机时时新建的结点为 x x x,则结点 x , l i n k ( x ) , l i n k ( l i n k ( x ) ) , . . . , 1 x,link(x),link(link(x)),...,1 x,link(x),link(link(x)),...,1的字串集的并集为 p r e ( i ) pre(i) pre(i)的后缀集。
令
l
a
s
t
last
last表示构建
p
r
e
(
i
−
1
)
pre(i-1)
pre(i−1)时新建的结点,令
n
o
w
now
now表示构建
p
r
e
(
i
)
pre(i)
pre(i)时新建的结点,由
l
e
n
(
n
o
w
)
=
l
e
n
(
l
a
s
t
)
+
1
len(now)=len(last)+1
len(now)=len(last)+1,再分以下情况考虑:
n
e
x
(
l
a
s
t
)
(
s
i
)
=
n
u
l
l
nex(last)(s_i)=null
nex(last)(si)=null:在
p
r
e
(
i
−
1
)
pre(i-1)
pre(i−1)所属的等价类后面新加字符
s
i
s_i
si,新的子串集属于等价类
n
o
w
now
now,令
n
e
x
(
l
a
s
t
)
(
s
i
)
=
n
o
w
nex(last)(s_i)=now
nex(last)(si)=now即可。由引理,继续递归检查
l
a
s
t
last
last到
1
1
1的
l
i
n
k
link
link路径上的结点。
在
l
a
s
t
last
last的
l
i
n
k
link
link路径上存在结点
p
p
p满足
n
e
x
(
p
)
(
s
i
)
=
q
≠
n
u
l
l
nex(p)(s_i)=q\neq null
nex(p)(si)=q=null,且
p
p
p的任意前驱结点
x
x
x都满足
n
e
x
(
x
)
(
s
i
)
=
n
u
l
l
nex(x)(s_i)=null
nex(x)(si)=null:等价类
p
p
p的字串集加上字符
s
i
s_i
si后属于等价类
q
q
q,但是等价类
q
q
q中为
p
r
e
(
i
)
pre(i)
pre(i)后缀的字串集新加入值
i
i
i的
e
n
d
p
o
s
endpos
endpos,又分两种情况讨论:
当
l
e
n
(
q
)
=
l
e
n
(
p
)
+
1
len(q)=len(p)+1
len(q)=len(p)+1,则
q
q
q的字符集都为
p
r
e
(
i
)
pre(i)
pre(i)的后缀,又由
l
i
n
k
link
link的定义,令
l
i
n
k
(
n
o
w
)
=
q
link(now)=q
link(now)=q即可。
当
l
e
n
(
q
)
>
l
e
n
(
p
)
+
1
len(q)>len(p)+1
len(q)>len(p)+1,则
q
q
q中长度大于
l
e
n
(
p
)
+
1
len(p)+1
len(p)+1的不是
p
r
e
(
i
)
pre(i)
pre(i)的后缀,因此把长度
≤
l
e
n
(
p
)
+
1
\le len(p)+1
≤len(p)+1这部分分离出来,构成一个新的结点
n
q
nq
nq,不难证明
l
i
n
k
(
q
)
=
l
i
n
k
(
n
o
w
)
=
n
q
,
n
e
x
(
n
q
)
=
n
e
x
(
s
)
link(q)=link(now)=nq,nex(nq)=nex(s)
link(q)=link(now)=nq,nex(nq)=nex(s)
,
l
i
n
k
(
n
q
)
=
p
,link(nq)=p
,link(nq)=p。而对于
p
p
p到
1
1
1的
l
i
n
k
link
link路径上的其它结点
o
p
op
op,若
n
e
x
(
o
p
)
(
s
i
)
=
q
nex(op)(s_i)=q
nex(op)(si)=q,则令
n
e
x
(
o
p
)
(
s
i
)
=
n
q
nex(op)(s_i)=nq
nex(op)(si)=nq,否则其一定属于
l
i
n
k
(
s
)
link(s)
link(s)路径上,也属于新构建后
l
i
n
k
(
n
o
w
)
link(now)
link(now)的路径上(显然
l
i
n
k
(
s
)
link(s)
link(s)之后的字串集都为
p
r
e
(
i
)
pre(i)
pre(i)的后缀)。
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int last=1,tot=1,fa[N],sum[N],endpos[N],len[N],t[N][26];
char s[N];
void add(int x)
{
int p=last,n=++tot;
last=n;endpos[n]=1;len[n]=len[p]+1;
while(p&&!t[p][x]) t[p][x]=n,p=fa[p];
if(!p) fa[n]=1;
else
{
int q=t[p][x];
if(len[p]+1==len[q]) fa[n]=q;
else
{
int nq=++tot;
for(int i=0;i<26;i++)
t[nq][i]=t[q][i];
fa[nq]=fa[q];fa[q]=nq;
fa[n]=nq;len[nq]=len[p]+1;
while(p&&t[p][x]==q) t[p][x]=nq,p=fa[p];
}
}
}
queue<int>q;
int main()
{
scanf("%s",s+1);
for(int i=1;s[i];i++)
add(s[i]-'a');
for(int i=2;i<=tot;i++)
sum[fa[i]]++;
for(int i=2;i<=tot;i++)
if(!sum[i]) q.push(i);
while(!q.empty())
{
int u=q.front();q.pop();
endpos[fa[u]]+=endpos[u];
sum[fa[u]]--;
if(!sum[fa[u]]) q.push(fa[u]);
}
int ans=0;
for(int i=2;i<=tot;i++)
if(endpos[i]!=1)
ans=max(ans,endpos[i]*len[i]);
printf("%d\n",ans);
}
8.广义后缀自动机
当构造多串的后缀自动机时,只要注意,上述是用
p
r
e
(
i
−
1
)
pre(i-1)
pre(i−1)的自动机构造
p
r
e
(
i
)
pre(i)
pre(i)的自动机,令
p
r
e
(
i
−
1
)
+
c
1
=
p
r
e
(
i
)
pre(i-1)+c_1=pre(i)
pre(i−1)+c1=pre(i),如果再构建一个
p
r
e
(
i
−
1
)
+
c
2
=
p
pre(i-1)+c_2=p
pre(i−1)+c2=p的自动机,那么只需要把
p
r
e
(
i
−
1
)
+
c
1
pre(i-1)+c_1
pre(i−1)+c1和
p
r
e
(
i
−
1
)
+
c
2
pre(i-1)+c_2
pre(i−1)+c2看成不同等价类即可(虽然它们的
e
n
d
p
o
s
endpos
endpos相同,但是其字符串不相等)。
简单来说,只要对等价类定义做点小小的修改:
t
1
,
t
2
t_1,t_2
t1,t2属于同一等价类,必须满足
s
u
f
(
t
1
,
t
2
)
∣
∣
s
u
f
(
t
2
,
t
1
)
suf(t_1,t_2)||suf(t_2,t_1)
suf(t1,t2)∣∣suf(t2,t1),且
e
n
d
p
o
s
(
t
1
)
=
e
n
d
p
o
s
(
t
2
)
endpos(t_1)=endpos(t_2)
endpos(t1)=endpos(t2)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
string s[N];
int n,k;
ll ans[N];
int las=1,tot=1,sa[N],f[N],vis[N],num[N],sum[N],len[N],son[N][26],fa[N];
int work(int p,int c)
{
int nq=++tot,q=son[p][c];
len[nq]=len[p]+1;
fa[nq]=fa[q];fa[q]=nq;
memcpy(son[nq],son[q],sizeof(son[q]));
while(p&&son[p][c]==q) son[p][c]=nq,p=fa[p];
return nq;
}
int ins(int p,int c)
{
if(son[p][c])
{
int q=son[p][c];
if(len[q]==len[p]+1) return q;
else return work(p,c);
}
else
{
int np=++tot;len[np]=len[p]+1;
while(p&&!son[p][c]) son[p][c]=np,p=fa[p];
if(!p) fa[np]=1;
else
{
int q=son[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else fa[np]=work(p,c);
}
return np;
}
}
void solve()
{
int u;
for(int i=1;i<=n;i++)
{
u=1;
for(int j=0;j<s[i].size();j++)
{
u=son[u][s[i][j]-'a'];int p=u;
while(p&&vis[p]!=i) sum[p]++,vis[p]=i,p=fa[p];
}
}
for(int i=1;i<=tot;i++) num[len[i]]++;
for(int i=1;i<=tot;i++) num[i]+=num[i-1];
for(int i=tot;i>=1;i--) sa[num[len[i]]--]=i;
for(int i=2;i<=tot;i++)
u=sa[i],f[u]=f[fa[u]]+(sum[u]>=k?len[u]-len[fa[u]]:0);
for(int i=1;i<=n;i++)
{
u=1;
for(int j=0;j<s[i].size();j++)
u=son[u][s[i][j]-'a'],ans[i]+=f[u];
}
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>s[i];
las=1;
for(int j=0;j<s[i].size();j++)
las=ins(las,s[i][j]-'a');
}
solve();
for(int i=1;i<=n;i++)
printf(i==n?"%lld\n":"%lld ",ans[i]);
}