后缀数组SA 回文自动机PAM

后缀数组

两个数组:\(sa[]\)\(rk[]\)

\(sa[i]\)表示将所有后缀排第\(i\)小的后缀的编号(起始位置在哪里)
\(rk[i]\)表示以\(i\)为起始位置的后缀的排名。

这两个数组满足性质:\(sa[rk[i]]=rk[sa[i]]=i\)
oiwiki讲的很好,直接粘过来
oiwi

正常排序,帮助理解求\(sa\)过程

$nlog^2n$
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f;
const int maxn=1000005;
char s[maxn];
int n,w,sa[maxn],rk[maxn<<1|1],oldrk[maxn<<1|1];
bool cmp(int x,int y){
    return rk[x]==rk[y]?rk[x+w]<rk[y+w]:rk[x]<rk[y];
    //以rank[i]为第一关键字,rank[i+w]为第二关键字
}
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n;++i)sa[i]=i;//随便给个1-n的排序
    for(int i=1;i<=n;++i)rk[i]=s[i];//先按照第一个字母排个rank,只需要相对大小即可
    for(w=1;w<n;w<<=1){
        sort(sa+1,sa+n+1,cmp);
        for(int i=1;i<=n;++i)oldrk[i]=rk[i];
        for(int p=0,i=1;i<=n;++i){
            if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w])rk[sa[i]]=p;
            else rk[sa[i]]=++p;
            //判断条件和p是为了去重
        }
    }
    for(int i=1;i<=n;++i)printf("%d ",sa[i]);
    return 0;
}

优化,基数排序

不卡常$nlogn$
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f;
const int maxn=1000005;
char s[maxn];
int n,w,sa[maxn],rk[maxn<<1|1],id[maxn],m=300,oldrk[maxn<<1|1],cnt[maxn];
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n;++i)++cnt[rk[i]=s[i]];//按照第一个字母排个序
    for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;--i)sa[cnt[rk[i]]--]=i;
    m=max(m,n);
    for(w=1;w<n;w<<=1){
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;++i)id[i]=sa[i];
        for(int i=1;i<=n;++i)++cnt[rk[id[i]+w]];
        for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;--i)sa[cnt[rk[id[i]+w]]--]=id[i];//基数排序第二关键字

        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;++i)id[i]=sa[i];
        for(int i=1;i<=n;++i)++cnt[rk[id[i]]];
        for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;--i)sa[cnt[rk[id[i]]]--]=id[i];//基数排序第一关键字

        for(int i=1;i<=n;++i)oldrk[i]=rk[i];//与之前相同
        for(int p=0,i=1;i<=n;++i){
            if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w])rk[sa[i]]=p;
            else rk[sa[i]]=++p;
        }
    }
    for(int i=1;i<=n;++i)printf("%d ",sa[i]);
    return 0;
}

卡常+亿点注释

卡常$nlogn$
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f;
const int maxn=1000005;
char s[maxn];
int n,w,sa[maxn],rk[maxn<<1|1],id[maxn],m=300,oldrk[maxn<<1|1],cnt[maxn],px[maxn];
bool cmp(int x,int y,int w){
    return oldrk[x]==oldrk[y]&&oldrk[x+w]==oldrk[y+w];
}
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n;++i)++cnt[rk[i]=s[i]];//按照第一个字母排个序,这个时候只需要相对大小关系,里面的东西暂时不太合法
    for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];//桶累加
    for(int i=n;i>=1;--i)sa[cnt[rk[i]]--]=i;//cnt[..]为i的新rank 其实是sa[rk[i]]=i
    m=max(m,n);//m为桶的值域

    for(w=1;w<n;w<<=1){
        int p=0;
        for(int i=n;i>n-w;--i)id[++p]=i;//空串直接记录,按照第二关键字他们最小且没有顺序,所以随便给个顺序即可
        for(int i=1;i<=n;++i)if(sa[i]>w)id[++p]=sa[i]-w;//第二关键字,用后缀i的当前sa的顺序去更新后缀sa[i]-w

        for(int i=1;i<=m;++i)cnt[i]=0;//清桶
        for(int i=1;i<=n;++i)++cnt[px[i]=rk[id[i]]];//按照原来的rank排序,px临时存一下rk[id[i]],减少不必要的内存访问
        for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];//累加
        for(int i=n;i>=1;--i)sa[cnt[px[i]]--]=id[i];//cnt[..]为sa[i]的新rank 而为了避免改乱,所以原来的sa用id来代替,这就是上面和这里赋值用id的原因
        
        for(int i=1;i<=n;++i)oldrk[i]=rk[i];//copy一下,cmp用
        p=0;
        for(int i=1;i<=n;++i)rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p;//写cmp减少不必要的内存访问,去重

        if(p==n)break;//已经排完就不用管了
        m=p;//值域优化
    }
    for(int i=1;i<=n;++i)printf("%d ",sa[i]);
    return 0;
}

最长公共前缀\(LCP\)

定义\(LCP(i,j)\) 表示\(sa_i\)个和\(sa_j\)个的两个后缀的最长公共前缀。

性质

\(LCP(i,j)=LCP(j,i)\)

\(LCP(i,i)=len(sa_i)=n−sa_i+1\)

\(LCP Lemma :\)

\(LCP(i,j)=min⁡(LCP(i,k),LCP(k,j))(1≤i≤k≤j≤n)\)

\(LCP Theorem:\)

$ LCP(i,j)=min⁡(LCP(k,k−1))(1<i<k≤j≤n)$

求法

定义

\(height[i]=lcp(sa[i],sa[i-1])\)

引理:

\(height[rk[i]]\ge height[rk[i-1]]-1\)

比较感性的证明观察一下

image

LCP
void LCP(){
    for(int i=1,k=0;i<=n;++i){
        if(rk[i]==0)continue;
        if(k)--k;//height[i]>=heigh[i-1]-1;
        while(s[i+k]==s[sa[rk[i]-1]+k])++k;
        height[rk[i]]=k;
    }
}

回文自动机PAM

code
#include<cstring>
#include<iostream>
using namespace std;
const int maxn = 5e5+55;
char s[maxn];
int ch[maxn][27], len[maxn], fail[maxn], dep[maxn];
int main(){
	cin >> s + 1;
	s[0] = '#';
	int n = strlen(s + 1);
	int las = 0, ans = 0, cnt = 1;
	fail[0] = 1;len[1] = -1;
	for(int i = 1 ; i <= n; ++i){
		while(s[i - len[las] - 1] != s[i])las = fail[las];
		if(!ch[las][s[i] - 'a']){
			len[++cnt] = len[las] + 2;
			int j = fail[las];
			while(s[i - len[j] - 1] != s[i])j = fail[j];
			fail[cnt] = ch[j][s[i] - 'a'];
			ch[las][s[i] - 'a'] = cnt;
		}
		las = ch[las][s[i] - 'a'];
		cout << (ans = dep[las]) << " " ;
	}
	return 0;
}
posted @ 2022-06-19 13:57  Chen_jr  阅读(51)  评论(0编辑  收藏  举报