HihoCoder 后缀数组入门1-4 题解

#1403 : 后缀数组一·重复旋律

后缀数组做法:

后缀数组中Height[i]代表着后缀i跟后缀i-1的LCP(最长公共前缀),也就是出现次数至少为2次的最长子串。

而要求出现次数最少为K次的最长子串,即求Height[i]数组中所有长度为K-1的区间中的最小值的最大值。

用后缀数组处理出Height数组之后,那就有好几种方法处理了,这里使用单调队列。

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+11;
int a[N];
int sa[N],ra[N],height[N];
int cntr[N],tsa[N];
//cntr"桶",tsa[i]临时数组,不同情况意义不同 
deque<int> q;
//根据第一关键字ra[i]和以及按第二关键字[i+l]排好序的tsa进行基数排序 
void sasort(int n,int m){
    for(int i=0;i<=m;i++) cntr[i]=0;
    for(int i=1;i<=n;i++) cntr[ra[i]]++;
    for(int i=1;i<=m;i++) cntr[i]+=cntr[i-1];
    for(int i=n;i>=1;i--) sa[cntr[ra[tsa[i]]]--]=tsa[i];
}
void getsa(int n){
    int m=100;//初始的数据大小,比如ascii码的话,一般为127 
    for(int i=1;i<=n;i++) ra[i]=a[i],tsa[i]=i;
    sasort(n,m);
    for(int l=1;l<=n;l<<=1){
        int num=0;
        //[n-l+1,n]的数第二关键字为0,按第二关键字排序肯定是在前面 
        for(int i=n-l+1;i<=n;i++) tsa[++num]=i;
        // 如果sa[i]>l,那么它可以作为sa[i]-l的第二关键字 
        for(int i=1;i<=n;i++) if(sa[i]>l) tsa[++num]=sa[i]-l; 
        sasort(n,m);
        //重复空间利用,用tsa暂时存下上一轮的ra用于比较求出新的ra
        memcpy(tsa,ra,(n+1)*sizeof(int));
        ra[sa[1]]=1;
        for(int i=2;i<=n;i++){
            ra[sa[i]]=ra[sa[i-1]];
            if(tsa[sa[i]]!=tsa[sa[i-1]]||tsa[sa[i]+l]!=tsa[sa[i-1]+l]) ra[sa[i]]++;
        }
        m=ra[sa[n]];
        if(m>=n) break;
    }
    for(int i=1,j=0;i<=n;i++){
        if(j) j--;
        while(a[i+j]==a[sa[ra[i]-1]+j]) j++;
        height[ra[i]]=j;
    }
    return ;
}
int solve(int n,int k){
    while(!q.empty()) q.pop_back();
    for(int i=1;i<k;i++){
        while(!q.empty()&&height[i]<=height[q.back()]) q.pop_back();
        q.push_back(i); 
    }
    int ans=0;
    for(int i=k;i<=n;i++){
        while(!q.empty()&&i-q.front()>=k-1) q.pop_front();
        while(!q.empty()&&height[i]<=height[q.back()]) q.pop_back();
        q.push_back(i);
        ans=max(ans,height[q.front()]);
    }
    return ans;
}
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    a[n+1]=-1;
    getsa(n);
    printf("%d\n",solve(n,k));
    return 0;
} 
出现次数最少为K次的最长子串

因为我是先学的后缀自动机,在学的后缀数组,所有对前三题我也尝试了后缀自动机的做法。

后缀自动机中,endpos[i]为i状态中子串出现的次数,而len[i]为i状态的最长子串长度。具体求法在后缀自动机中再介绍。

处理出endpos和len之后,我们就统计下每个出现次数的最长子串长度,然后k~n找最大值即可。

#include<bits/stdc++.h>
using namespace std;
const int N=4e4+11;
struct Sam{
    int size,last,len[N],link[N],trans[N][111];
    int endpos[N];
    Sam(){
        size=last=1;
        memset(trans,0,sizeof(trans));
    } 
    void extend(int x){
        int cur=++size,u;
        endpos[cur]=1;
        len[cur]=len[last]+1;
        for(u=last;u&&!trans[u][x];u=link[u]) trans[u][x]=cur;
        if(!u) link[cur]=1;
        else{
            int v=trans[u][x];
            if(len[v]==len[u]+1) link[cur]=v;
            else{
                int clone=++size;
                len[clone]=len[u]+1;
                link[clone]=link[v];
                memcpy(trans[clone],trans[v],sizeof(trans[v]));
                for(;u&&trans[u][x]==v;u=link[u]) trans[u][x]=clone;
                link[cur]=link[v]=clone;
            }
        }
        last=cur;
    }
}A;
int a[N],ans[N];
vector<int> vv[N];
void dfs(int u){
    int lenv=vv[u].size();
    for(int i=0;i<lenv;i++){
        int v=vv[u][i];
        dfs(v);
        A.endpos[u]+=A.endpos[v];
    }
}
void solve(){
    for(int i=1;i<=A.size;i++) vv[A.link[i]].push_back(i);
    dfs(1);
    for(int i=1;i<=A.size;i++) ans[A.endpos[i]]=max(ans[A.endpos[i]],A.len[i]);
}
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) A.extend(a[i]);
    solve();
    int res=0;
    for(int i=k;i<=n;i++) res=max(res,ans[i]);
    printf("%d\n",res);
    return 0;
}
哪里自动了

#1407 : 后缀数组二·重复旋律2

后缀数组做法:

跟上题类似,但这次要找的是不重叠的子串。而如果存在某个长度的子串符合条件,那么比它的子串肯定也符合条件。

所以这个长度存在一个单调性,那么我们可以二分子串的长度,然后去判断这个长度合不合理。

假设当前二分的答案为x,对于存不存在至少出现两次长度为x的子串,直接便是看存不存在height[i]>=x。怎么判断存在不重复的呢。

我们把连续的height[i]>=x,对应的后缀分在同一组,这样就保证了该组中所有后缀两两之间的最长公共前缀都是不小于k的。注意,是连续的。

然后我们找出这一组中最大的sa,和最小的sa,即后缀在原串中相应的位置。而如果max{sa}-min{sa}大于等于x的话,就说明存在长度至少为x且不重叠的子串。

也就是至少min{sa}到min{sa}+x这一段,和max{sa}到max{sa}+x的这一段是相同的,而max{sa}-min{sa}>=x,这两段并不存在重叠部分。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+11;
int a[N];
int sa[N],ra[N],height[N];
int cntr[N],tsa[N];
void sasort(int n,int m){
    for(int i=0;i<=m;i++) cntr[i]=0;
    for(int i=1;i<=n;i++) cntr[ra[i]]++;
    for(int i=1;i<=m;i++) cntr[i]+=cntr[i-1];
    for(int i=n;i>=1;i--) sa[cntr[ra[tsa[i]]]--]=tsa[i];
}
void getsa(int n){
    int m=1000;
    for(int i=1;i<=n;i++) ra[i]=a[i],tsa[i]=i;
    sasort(n,m);
    for(int l=1;l<=n;l<<=1){
        int num=0;
        for(int i=n-l+1;i<=n;i++) tsa[++num]=i;
        for(int i=1;i<=n;i++) if(sa[i]>l) tsa[++num]=sa[i]-l; 
        sasort(n,m);
        swap(ra,tsa);
        ra[sa[1]]=1;
        for(int i=2;i<=n;i++){
            ra[sa[i]]=ra[sa[i-1]];
            if(tsa[sa[i]]!=tsa[sa[i-1]]||tsa[sa[i]+l]!=tsa[sa[i-1]+l]) ra[sa[i]]++;
        }
        m=ra[sa[n]];
        if(m>=n) break;
    }
    for(int i=1,j=0;i<=n;i++){
        if(j) j--;
        while(a[i+j]==a[sa[ra[i]-1]+j]) j++;
        height[ra[i]]=j;
    }
    return ;
}
bool check(int n,int k){
    int maxsa,minsa;
    for(int i=1;i<=n;i++){
        if(height[i]<k){
            maxsa=sa[i];
            minsa=sa[i];
        }else{
            maxsa=max(maxsa,sa[i]);
            minsa=min(minsa,sa[i]);
            if(maxsa-minsa>=k) return true;
        }
    }//将连续的height大于k归为一组判断,如果遇到中断的则重置
    return false;
}
int solve(int n){
    int l=1,r=n,mid,ans=0;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(n,mid)) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}
int main(){
    int n,k;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    getsa(n);
    printf("%d\n",solve(n));
    return 0;
}
至少出现两次的最长不重叠子串

后缀自动机做法:

在类似求endpos[i],我们同时可以求出maxpos[i],minpos[i],即每个状态的endpos集合中的最大值和最小值。

endpos[i]自然就是这个状态下子串的出现次数了,那么判断最长不重叠部分,在len[i]以及maxpos[i]-minpos[i]+1中取个最小值即可。

 

 

 如上图,存在两种情况,绿色部分就是不重叠部分的最长长度。

但这题不能用后缀自动机做,会超内存,可能是我不会优化吧,啊,so vegetable,所以代码就随便看看就好了。

扩展一下,如果是至少出现不重复的子串的个数的话,后缀自动机就得用遍历所有长度,此时是n方的复杂度。

而后缀自动机的话,直接用不重叠部分的最长长度,减去该状态中最短子串长度即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+11,inf=0x3f3f3f3f;
struct Sam{
    int size,last,len[N],link[N],trans[N][1011];
    int endpos[N],minpos[N],maxpos[N];
    Sam(){
        size=last=1;
        memset(trans,0,sizeof(trans));
    } 
    void extend(int pos,int x){
        int cur=++size,u;
        endpos[cur]=1;
        len[cur]=len[last]+1;
        minpos[cur]=maxpos[cur]=pos;
        for(u=last;u&&!trans[u][x];u=link[u]) trans[u][x]=cur;
        if(!u) link[cur]=1;
        else{
            int v=trans[u][x];
            if(len[v]==len[u]+1) link[cur]=v;
            else{
                int clone=++size;
                len[clone]=len[u]+1;
                minpos[cur]=inf;maxpos[cur]=-1;
                link[clone]=link[v];
                memcpy(trans[clone],trans[v],sizeof(trans[v]));
                for(;u&&trans[u][x]==v;u=link[u]) trans[u][x]=clone;
                link[cur]=link[v]=clone;
            }
        }
        last=cur;
    }
}A;
int a[N],ans[N];
vector<int> vv[N];
void dfs(int u){
    int lenv=vv[u].size();
    for(int i=0;i<lenv;i++){
        int v=vv[u][i];
        dfs(v);
        A.endpos[u]+=A.endpos[v];
        A.minpos[u]=min(A.minpos[u],A.minpos[v]);
        A.maxpos[u]=max(A.maxpos[u],A.maxpos[v]);
    }
}
int solve(){
    for(int i=1;i<=A.size;i++) vv[A.link[i]].push_back(i);
    dfs(1);
    int ans=0;
    for(int i=1;i<=A.size;i++) ans=max(ans,min(A.len[i],A.maxpos[i]-A.minpos[i]+1));
//    出现两次以上,不重叠的子串个数 
//    int dis=A.maxpos[i]-A.minpos[i];
//    int mi=min(dis,A.len[i]);
//    int milen=A.len[i]-A.len[A.link[i]]+1;
//    ans+=max(0,mi-milen+1);
    return ans;
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) A.extend(i,a[i]);
    printf("%d\n",solve());
    return 0;
}
vegetable

#1415 : 后缀数组三·重复旋律3

后缀数组做法:

我们将第二个字符串接在第一个字符串后面,并且两者中间用一个数据中不会出现的字符(比如说#)分隔开。

此时求出排名相邻的不在同一个字符串的height中的最大值即可。

因为如果两个后缀在不同串中,计算它们最长前缀时必定要跨越过那些相邻height值。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+11;
char s1[N],s2[N];
int a[N];
int sa[N],ra[N],height[N];
int cntr[N],tsa[N];
void sasort(int n,int m){
    for(int i=0;i<=m;i++) cntr[i]=0;
    for(int i=1;i<=n;i++) cntr[ra[i]]++;
    for(int i=1;i<=m;i++) cntr[i]+=cntr[i-1];
    for(int i=n;i>=1;i--) sa[cntr[ra[tsa[i]]]--]=tsa[i];
}
void getsa(int n){
    int m=132;
    for(int i=1;i<=n;i++) ra[i]=a[i],tsa[i]=i;
    sasort(n,m);
    for(int l=1;l<=n;l<<=1){
        int num=0;
        for(int i=n-l+1;i<=n;i++) tsa[++num]=i;
        for(int i=1;i<=n;i++) if(sa[i]>l) tsa[++num]=sa[i]-l; 
        sasort(n,m);
        swap(ra,tsa);
        ra[sa[1]]=1;
        for(int i=2;i<=n;i++){
            ra[sa[i]]=ra[sa[i-1]];
            if(tsa[sa[i]]!=tsa[sa[i-1]]||tsa[sa[i]+l]!=tsa[sa[i-1]+l]) ra[sa[i]]++;
        }
        m=ra[sa[n]];
        if(m>=n) break;
    }
    for(int i=1,j=0;i<=n;i++){
        if(j) j--;
        while(a[i+j]==a[sa[ra[i]-1]+j]) j++;
        height[ra[i]]=j;
    }
    return ;
}
int main(){
    scanf("%s%s",s1+1,s2+1);
    int lens1=strlen(s1+1),lens2=strlen(s2+1);
    for(int i=1;i<=lens1;i++) a[i]=s1[i];
    a[lens1+1]+='#';
    for(int i=1;i<=lens2;i++) a[lens1+1+i]=s2[i];
    getsa(lens1+lens2+1);
    int ans=0;
    for(int i=1;i<=lens1+lens2+1;i++){
        if((sa[i]<=lens1&&sa[i-1]>lens1)||(sa[i]>lens1&&sa[i-1]<=lens1))
            ans=max(ans,height[i]);
    }
    printf("%d\n",ans);
    return 0;
}
两个串的最长公共子串

后缀自动机做法:

在后缀自动机部分里会涉及到,这里就不重复了。

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+11;
struct Sam{
    int size,last,len[N],link[N],trans[N][31];
    Sam(){
        size=last=1;
    }
    void extend(int x){
        int cur=++size,u;
        len[cur]=len[last]+1;
        for(u=last;u&&!trans[u][x];u=link[u]) trans[u][x]=cur;
        if(!u) link[cur]=1;
        else{
            int v=trans[u][x];
            if(len[v]==len[u]+1) link[cur]=v;
            else{
                int clone=++size;
                len[clone]=len[u]+1;
                link[clone]=link[v];
                memcpy(trans[clone],trans[v],sizeof(trans[v]));
                for(;u&&trans[u][x]==v;u=link[u]) trans[u][x]=clone;
                link[cur]=link[v]=clone;
            }
        }
        last=cur;
    }
}A;
char s[N];
int main(){
    scanf("%s",s);
    int lens=strlen(s);
    for(int i=0;i<lens;i++) A.extend(s[i]-'a');
    scanf("%s",s);
    lens=strlen(s);
    int u=1,lcs=0,ans=0,x;
    for(int i=0;i<lens;i++){
        x=s[i]-'a';
        while(u!=1&&!A.trans[u][x]) u=A.link[u],lcs=A.len[u];
        if(A.trans[u][x]) lcs++,u=A.trans[u][x];
        else u=1,lcs=0;
    //    printf("%d %d\n",i,lcs);
        ans=max(ans,lcs);
    }
    printf("%d\n",ans);
    return 0;
} 
就硬搬

#1419 : 后缀数组四·重复旋律4

 这题只能后缀数组来做,(对于我来说)。

我们可以通过枚举循环串长度,然后再枚举起始位置来得到k,假设当前枚举的长度为l,位置为i,那么k=lcp(i,i+l)/l+1

 如长图,蓝色部分即为l,i后缀(上面的),跟i+l后缀(下面的)比较的话,一开始当然是蓝色的跟绿色的这段比较

如果他们相等,接下来就是绿色的跟紫色的比较,(注意,他们应该是连续的,只是为了好看,没画连续,虽然也不好看。)

求出他们最长相等的长度,而这一段都是一开始蓝色那部分在循环,所以再除去循环串的长度+1就是答案了。

但很明显,这样的复杂度是n方,我们需要优化一下,也就是不用枚举每个起始位置,只枚举是l倍数的位置。

HihoCoder讲得很好 ,直接搬过来了,侵删。。。。。。。。。。

小Ho:道理是这么个道理。不过如果最优串的开始位置不在l的倍数上呢?

小Hi:即使不是,问题也会太糟糕,假如说最优串位置在x,可以想象我们会枚举到x之后的一个最近位置p,p是l的倍数。并且我们计算出了Suffix(p)和Suffix(p+l)的LCP,lcp(l, p)那么此时的k(l, p)=lcp(l, p)/l+1。

小Hi:对于被我们略过的k(l, p-1), k(l, p-2) ... k(l, p-l+1),它们的上限是k(l, p)+1。

小Ho:没错。因为它们的起始位置距离p不超过l,所以最多比Suffix(p)增加一个循环节。

小Hi:其次,如果k(l, p-1), k(l, p-2) ... k(l, p-l+1)中有一个的值是k(l, p)+1的话,那么k(l, p - l + lcp(l, p) mod l)一定等于k(l, p)+1。(mod是取余运算)

小HO:为什么呢?

小Hi:举个例子,比如串XaYcdabcdabcd(XY各代表一个不确定的字符,具体代表的字符会影响最后答案,我们后面会分析到),当我们考虑l=4的时候,第一次枚举p=4的起始位置,会求出cdabcdabcd和cdabcd的lcp(4, 4)=6,k(4, 4)=2。根据上面的论断,只有当k(l, p - l + lcp(l, p) mod l)=k(4, 4 - 4 + 6 mod 4)=k(4, 2)=3时,k(4, 1), k(4, 2)和k(4, 3)中才会有3。首先我们可以判断k(4, 3)一定不可能等于3,因为无论Y是哪个字符,Ycdabcdabcd和bcdabcd的LCP即lcp(4, 3)最大是7,不到8。 其次如果k(4, 2) ≠ 3,那么k(4, 1)也没戏。因为如果k(4, 2) ≠ 3,说明aY和ab匹配不上,这时无论X是哪个字符,XaY和dab匹配不上,lcp(4, 1) < l,k(4, 1) = 1。

小Ho:哦,我有点明白了。k(l, p - l + lcp(l, p) mod l)是一个分界线,右边的值因为LCP不够大,一定不能增加一个循环节。并且如果k(l, p - l + lcp(l, p) mod l)没有增加循环节的话,说明[p - l + lcp(l, p) mod l, p]这段中间匹配出错,左边的lcp也跟着雪崩,更不可能增加循环节了。

所以现在的复杂度就是n/1+n/2+n/3+...1,经典求极限,也就是nlogn,(准确的好像是nlnn)

哦,最后一点,查询lcp(i,i+l)也就是RMQ问题嘛,懒得建线段树,这里用ST算法实现。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=3e5+11;
 4 char a[N];
 5 int lg2[N],st[N][21];
 6 int sa[N],ra[N],height[N];
 7 int cntr[N],tsa[N];
 8 void sasort(int n,int m){
 9     for(int i=0;i<=m;i++) cntr[i]=0;
10     for(int i=1;i<=n;i++) cntr[ra[i]]++;
11     for(int i=1;i<=m;i++) cntr[i]+=cntr[i-1];
12     for(int i=n;i>=1;i--) sa[cntr[ra[tsa[i]]]--]=tsa[i];
13 }
14 void getsa(int n){
15     int m=127;
16     for(int i=1;i<=n;i++) ra[i]=a[i],tsa[i]=i;
17     sasort(n,m);
18     for(int l=1;l<=n;l<<=1){
19         int num=0;
20         for(int i=n-l+1;i<=n;i++) tsa[++num]=i;
21         for(int i=1;i<=n;i++) if(sa[i]>l) tsa[++num]=sa[i]-l; 
22         sasort(n,m);
23         swap(ra,tsa);
24         ra[sa[1]]=1;
25         for(int i=2;i<=n;i++){
26             ra[sa[i]]=ra[sa[i-1]];
27             if(tsa[sa[i]]!=tsa[sa[i-1]]||tsa[sa[i]+l]!=tsa[sa[i-1]+l]) ra[sa[i]]++;
28         }
29         m=ra[sa[n]];
30         if(m>=n) break;
31     }
32     for(int i=1,j=0;i<=n;i++){
33         if(j) j--;
34         while(a[i+j]==a[sa[ra[i]-1]+j]) j++;
35         height[ra[i]]=j;
36     }
37     return ;
38 }
39 void getst(int n){
40     lg2[1]=0;
41     for(int i=2;i<=n;i++) lg2[i]=lg2[i/2]+1;
42     for(int i=1;i<=n;i++) st[i][0]=height[i];
43     for(int j=1;j<=lg2[n];j++){
44         for(int i=1;i+(1<<j)-1<=n;i++)
45             st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
46     }
47 }
48 int getlcp(int l,int r){
49     l=ra[l];r=ra[r];
50     if(l>r) swap(l,r);
51     l++;
52     int k=lg2[r-l+1];
53     return min(st[l][k],st[r-(1<<k)+1][k]);
54 }
55 int solve(int n){
56     getst(n);
57     int ans=0;
58     for(int l=1;l<=n;l++){
59         for(int i=1;i+l<=n;i+=l){
60             int r=getlcp(i,i+l);
61             ans=max(ans,r/l+1);
62             if(i>l-r%l){
63                 ans=max(ans,getlcp(i-l+r%l,i+r%l)/l+1);
64             }
65         }
66     }
67     return ans;
68 }
69 int main(){
70     while(~scanf("%s",a+1)){
71         int lena=strlen(a+1);
72         getsa(lena);
73         printf("%d\n",solve(lena));
74     }
75     return 0;
76 }
强啊后缀数组

 

posted @ 2020-07-19 03:10  新之守护者  阅读(180)  评论(0编辑  收藏  举报