省选算法学习-后缀数组+后缀自动机+后缀树

其实很久以前就学了这两个东西......但是一直懒得写,今天补一补

后缀数组

基础部分不讲了,放个板子在这

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后缀自动机上的位置

posted @ 2018-12-13 20:36  dedicatus545  阅读(330)  评论(0编辑  收藏  举报