bzoj 2251(后缀数组/后缀自动机)

Posted chen-jr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj 2251(后缀数组/后缀自动机)相关的知识,希望对你有一定的参考价值。

题意:

给你一个长度为n的01串,问你这个串的所有子串中,出现次数大于1的子串的出现次数,最后按照字典序输出。

分析:

对于这个题目,我们显然可以用两种处理后缀的数据结构进行处理。

1:后缀自动机:

个人觉得在这个题中,用后缀自动机去解决会相对来说比较好理解。

我们知道,在后缀自动机上的结点状态\(st\),若前一个状态通过字符\(c\)\(st\)相连,那么结点\(st\)表示的是\(endpos\)相同的子串的集合。而该点的\(endpos\)则代表的是以\(c\)为结尾的串的出现的位置集合。因此我们发现,对于一个结点\(st\),它的\(endpos\)集合的大小即是当前结点对应的子串的出现次数。那么显然,我们只需要把每一个状态\(st\)\(|endpos|\)求出即可。

而要求\(|endpos|\),我们只需要把\(parent\)树反向建出来,自底向上去更新子树的大小即是\(|endpos|\)

最后我们只需要在后缀自动机上贪心的根据字典序对每一个子串进行搜索即可。总的时间复杂度为\(\mathcalO(n^2)\)

2:后缀数组:

首先大概有这样的一个性质:对于两个不相同的后缀\(u\),\(v\),如果\(lcp(u,v)\)不为\(0\),那么字符串\(u\)中长度在\([1,lcp(u,v)]\)范围内的前缀必定在\(v\)中出现过至少一次。(因为每一个后缀都各不相同,而如果出现相同的前缀,那么这个前缀必定在原串出现至少\(2\)次)

那么我们考虑将所有的后缀按照字典序进行排序。因为所有的后缀都按照字典序进行排序了,那么如果存在一个串,满足:\(lcp(i,i-1)\le lcp(i+1,1)\),那么$ Str[rk[i]] $ 这个串的前缀必定也在串 $ Str[rk[i+1]] $ 出现过。因此,我可以按照字典序枚举每一个后缀 $ rk[i] $ ,并分别枚举长度为 $ 1 \le j \le height[i] $ 的子串,我们发现,如果在后面的位置 $ k (i+1 \le k \le n) $ 中出现 $ height[k] \ge j $ 那么说明长度为 \(j\) 的子串也会在第 \(k\) 个后缀中出现,因此我们只需要记录第一个不满足 $ height[k] \ge j $ 的位置 \(k\) ,那么最后的答案即为 $ k-i+1 $ 。

这样的整体时间复杂度为\(\mathcalO(n^2)\)

代码:

SAM:

// luogu-judger-enable-o2
#include <bits/stdc++.h>
#define maxn 10005
using namespace std;
char str[maxn];
vector<int>res;
struct SAM
    int next[maxn*2][2],fa[maxn*2],len[maxn*2];
    int last,cnt;
    int cntA[maxn*2],A[maxn*2];
    int num[maxn*2];
    void clear()
        last=cnt=1;
        fa[1]=len[1]=0;
        memset(next[1],0,sizeof(next[1]));
    
    void init(char *s)
        while(*s)
            Insert(*s-'0');
            s++;
        
    
    void Insert(int c)
        int p=last;
        int np=++cnt;
        memset(next[cnt],0,sizeof(next[cnt]));
        len[np]=len[p]+1;
        last=np;
        while(p&&!next[p][c]) next[p][c]=np, p=fa[p];
        if(!p) fa[np]=1;
        else
            int q=next[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else
                int nq=++cnt;
                len[nq]=len[p]+1;
                memcpy(next[nq],next[q],sizeof(next[q]));
                fa[nq]=fa[q];
                fa[np]=fa[q]=nq;
                while(next[p][c]==q) next[p][c]=nq, p=fa[p];
            
        
    
    void build()
        memset(cntA,0,sizeof(cntA));
        memset(num,0,sizeof(num));
        int n=strlen(str);
        for(int i=1;i<=cnt;i++) cntA[len[i]]++;
        for(int i=1;i<=n;i++) cntA[i]+=cntA[i-1];
        for(int i=cnt;i>=1;i--) A[cntA[len[i]]--]=i;
        int tmp=1;
        for(int i=0;i<n;i++)
            num[tmp=next[tmp][str[i]-'0']]=1;
        
        for(int i=cnt;i>=1;i--)
            int x=A[i];
            num[fa[x]]+=num[x];
        
    
    void dfs(int x)
        if(num[x]>1&&x!=1) res.push_back(num[x]);
        for(int i=0;i<2;i++)
            if(next[x][i]!=0)
                dfs(next[x][i]);
        
    
sam;
int main()

    int n;
    scanf("%d%s",&n,str);
    sam.clear();
    sam.init(str);
    sam.build();
    sam.dfs(1);
    for(auto it:res)
        printf("%d\n",it);
    
    return 0;

SA:

#include <bits/stdc++.h>
#define maxn 10010
using namespace std;
int n,rk[maxn],sa[maxn],height[maxn],tmp[maxn],cnt[maxn];
char str[maxn];
void SA(int n,int m)
    int i,j,k;
    n++;
    for(i=0;i<n+5;i++) rk[i]=sa[i]=height[i]=tmp[i]=0;
    for(i=0;i<m;i++) cnt[i]=0;
    for(i=0;i<n;i++) cnt[rk[i]=str[i]]++;
    for(i=1;i<m;i++) cnt[i]+=cnt[i-1];
    for(i=0;i<n;i++) sa[--cnt[rk[i]]]=i;
    for(k=1;k<=n;k<<=1)
        for(i=0;i<n;i++)
            j=sa[i]-k;
            if(j<0) j+=n;
            tmp[cnt[rk[j]]++]=j;
        
        sa[tmp[cnt[0]=0]]=j=0;
        for(i=1;i<n;i++)
            if(rk[tmp[i]]!=rk[tmp[i-1]]||rk[tmp[i]+k]!=rk[tmp[i-1]+k])
                cnt[++j]=i;
            sa[tmp[i]]=j;
        
        memcpy(rk,sa,n*sizeof(int));
        memcpy(sa,tmp,n*sizeof(int));
        if(j>=n-1) break;
    
    //get height[]
    i=0,k=0,height[0]=0;
    for(j=rk[0];i<n-1;i++,k++)
        while(~k&&str[i]!=str[sa[j-1]+k])
            height[j]=k--;
            j=rk[sa[j]+1];
        
    

int main()

    int n;
    scanf("%d%s",&n,str);
    int len=strlen(str);
    SA(len,200);
    for(int i=2;i<=n;i++)
        for(int j=height[i-1]+1;j<=height[i];j++)
            int k=i;
            while(height[k]>=j) k++;
            if(k-i+1<=0) continue;
            printf("%d\n",k-i+1);
        
    
    return 0;

以上是关于bzoj 2251(后缀数组/后缀自动机)的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 3277 & bzoj 3473 串 —— 广义后缀自动机

BZOJ 1396: 识别子串( 后缀数组 + 线段树 )

bzoj 2251[2010Beijing Wc]外星联络

_bzoj1031 [JSOI2007]字符加密Cipher后缀数组

BZOJ2806CTSC2012Cheat 广义后缀自动机+二分+Dp

bzoj4199: [Noi2015]品酒大会 (并查集 && 后缀数组)