码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2019-08-26 15:03:39      阅读:73      评论:0      收藏:0      [点我收藏+]

标签:eof   c++   ast   表示   std   next   amp   printf   emc   

题意:

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

分析:

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

1:后缀自动机:

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

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

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

最后我们只需要在后缀自动机上贪心的根据字典序对每一个子串进行搜索即可。总的时间复杂度为\(\mathcal{O}(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 $ 。

这样的整体时间复杂度为\(\mathcal{O}(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(后缀数组/后缀自动机)

标签:eof   c++   ast   表示   std   next   amp   printf   emc   

原文地址:https://www.cnblogs.com/Chen-Jr/p/11412182.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!