省选算法学习-后缀数组+后缀自动机+后缀树
其实很久以前就学了这两个东西......但是一直懒得写,今天补一补
后缀数组
基础部分不讲了,放个板子在这
void bsort(){
int i;
for(i=0;i<=m;i++) book[i]=0;
for(i=1;i<=n;i++) book[rank[i]]++;
for(i=1;i<=m;i++) book[i]+=book[i-1];
for(i=n;i>=1;i--) sa[book[rank[tmp[i]]]--]=tmp[i];
}
void getsa(){
int k,cnt,i,j;m=127;
for(i=1;i<=n;i++) rank[i]=a[i],tmp[i]=i;
bsort();
for(k=1,cnt=1;cnt<n;m=cnt,k<<=1){
cnt=0;
for(i=1;i<=k;i++) tmp[++cnt]=n-k+i;
for(i=1;i<=n;i++) if(sa[i]>k) tmp[++cnt]=sa[i]-k;
bsort();
swap(rank,tmp);
rank[sa[1]]=cnt=1;
for(i=2;i<=n;i++)
rank[sa[i]]=(tmp[sa[i]]==tmp[sa[i-1]]&&tmp[sa[i]+k]==tmp[sa[i-1]+k])?cnt:++cnt;
}
k=0;
for(i=1;i<=n;height[rank[i++]]=k)
for((k?k--:k),j=sa[rank[i]-1];a[i+k]==a[j+k];k++);
}
应用嘛,还是很广泛的
本体可以后缀排序【废话】
求出height并且st表一下,就可以求内部lcp啦之类的,还可以套主席树使用
多串的时候可以考虑首尾相连中间加分隔符'$'然后建SA,操作也很多
听说还能套FFT一起用?我反正没见过
例题
套可持久化线段树的:十二省联考2019 字符串(虽然实际上是SAM题)
别的懒得放了......很多操作现想就好
后缀自动机
Candy?菊苣讲的非常好,放在这里:Candy?
理解稍微有点困难......但是把有些概念当显然成立的背下来会有很大帮助
每个节点上$right$集合是核心维护的东西,维护的是这个节点可以识别的子串的可能起点
对于每个节点$s$,都有一个范围$[min(s),max(s)]$
从$right$集合中的位置出发,长度在这个范围内的子串,被SAM识别以后就放在这个点!
SAM也有fail树,其实更常见叫$parent$ $tree$,相当于失配指针
对于节点$fa$和节点$s$,有性质:$max(fa)=min(s)-1$
同时,$right(fa)$是所有包含$right(s)$的集合中,最小的一个
两个基本用法:
往对应的儿子走,就是匹配串后面加个字符
往自己的fail走,就是匹配串前面删个字符
板子在这里
namespace sam{
int ch[2000010][26],fa[2000010],val[2000010],siz[2000010],lis[2000010],book[2000010],root,cnt,last;
void init(){root=cnt=last=1;val[1]=0;}
inline int newnode(int w){val[++cnt]=w;return cnt;}
void insert(int c){
int p=last,np=newnode(val[p]+1);siz[np]++;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=root;
else{
int q=ch[p][c];
if(val[q]==val[p]+1) fa[np]=q;
else{
int nq=newnode(val[p]+1);
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];
fa[np]=fa[q]=nq;
for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
last=np;
}
void sort(int n){
int i;
for(i=1;i<=cnt;i++) book[val[i]]++;
for(i=1;i<=n;i++) book[i]+=book[i-1];
for(i=cnt;i>=1;i--) lis[book[val[i]]--]=i;
for(i=cnt;i>=1;i--){
siz[fa[lis[i]]]+=siz[lis[i]];
}
}
}
广义后缀自动机
广义的东西,就是把很多串建成$trie$然后$bfs$插入,要记录$last$指针下来
维护技巧
$right$集合里很多信息都可以维护,不止大小,还可以状压维护属于哪个串之类的
然后,有个操作是不做最后面那个基数排序,而是每次插入完从$np$往上更新$siz$,遇到更新过的就$break$
这种时间复杂度上界好像是$O(nlogn)$,数据小(1e5)的可以用用,不要老是用
$fail$树可以套到$lct$上,然后可以动态加元素、动态$dp$之类的
建立反串的SAM,其parent树就是原串的后缀树!
对于一个子串$[l,r]$,可以用在后缀树上倍增的方式快速定位。
具体而言,我们每一次完成插入的时候记录$last$指向的节点:那个节点就是对应的后缀在后缀树上的位置
倍增的时候,从$l$对应的节点开始倍增,找到最浅的,$right$集合适应区间包含$r-l+1$的节点,就是这个子串在后缀树or后缀自动机上的位置