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

题意:

给你一个长度为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;
}

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

时间: 2024-08-06 10:16:00

bzoj 2251(后缀数组/后缀自动机)的相关文章

bzoj 3172 后缀数组|AC自动机

后缀数组或者AC自动机都可以,模板题. /************************************************************** Problem: 3172 User: BLADEVIL Language: C++ Result: Accepted Time:424 ms Memory:34260 kb ****************************************************************/ //By BLADEVI

hdu 4622 Reincarnation(后缀数组|后缀自动机|KMP)

Reincarnation Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others) Total Submission(s): 2138    Accepted Submission(s): 732 Problem Description Now you are back,and have a task to do: Given you a string s consist of lo

POJ2774Long Long Message (后缀数组&amp;后缀自动机)

问题: The little cat is majoring in physics in the capital of Byterland. A piece of sad news comes to him these days: his mother is getting ill. Being worried about spending so much on railway tickets (Byterland is such a big country, and he has to spe

后缀数组,目前比较赶进度,而且有点难,所以放到以后再来看

后缀数组 后缀数组2

bzoj 2251

第一道后缀数组 后缀数组要维护三个数组:sa(suffix array), rk(rank)和ht(height). 含义分别是: sa[i]:将后缀按照字典序排序后,第i大的后缀的起始位置. rk[i]:起始位置为i的后缀的排名. ht[i]:起始位置为i的后缀与排名为rk[i]-1的后缀的最长公共前缀. 对于任意一个字串,一定是某个后缀的前缀. 然后从sa[1]开始,统计字串,每个后缀sa[i]的可能的字串的个数(不与前面统计的重复)是:n-sa[i]+1-ht[sa[i]] 然后就这羊枚举

bzoj 2251: [2010Beijing Wc]外星联络 后缀数组

2251: [2010Beijing Wc]外星联络 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 424  Solved: 232[Submit][Status][Discuss] Description 小 P 在看过电影<超时空接触>(Contact)之后被深深的打动,决心致力于寻 找外星人的事业.于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星 人发来的信息.虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高 低电平将接收到

BZOJ 题目3172: [Tjoi2013]单词(AC自动机||AC自动机+fail树||后缀数组暴力||后缀数组+RMQ+二分等五种姿势水过)

3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MB Submit: 1890  Solved: 877 [Submit][Status][Discuss] Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. Input 第一个一个整数N,表示有多少个单词,接下来N行每行一个单词.每个单词由小写字母组成,N<=200,单词长度不超过10^6

BZOJ 2251 2010Beijing WC 外星联络 后缀数组/Trie树

题目大意 给出一个字符串,问这个字符串中出现过1次以上的子串的个数,按照子串的字典序输出. 思路 由于数据范围过小,这个题有两个解法. 基本的想法就是用后缀数组来进行后缀的排序,之后按照height数组扫就可以了.应该是挺快的. 但是注意到数据范围只有3000,因此我们只需要弄出所有的后缀拿出来建立一颗后缀Trie树就行了.最后DFS一次树种的所有节点. CODE SuffixArray #include <cstdio> #include <cstring> #include &

BZOJ 2754: [SCOI2012]喵星球上的点名 [后缀数组+暴力]

2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1906  Solved: 839[Submit][Status][Discuss] Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣.   假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那