• 后缀数组习题集


    suffix(i):从第i个字符开始的后缀

    sa[i]:后缀数组,“排第几的是谁”,即suffix(sa[i])<suffix(sa[i+1]),1<=i<n。

    ra[i]:名次数组,“你排第几”,ra[i]表示的是suffix(i)在所有后缀中从小到大排列的“名次”

    height[i]:suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。

    对于j和k,设rank[j]<rank[j],则有:

    suffix(j)和suffix(k)的最长公共前缀为height[ra[j]+1],height[ra[j]+2],height[ra[j]+3],...,height[rank[k]]中的最小值。

    后缀数组的应用有:

    1、最长公共前缀

    2.1、重复子串:可重叠最长重复子串、不可重叠最长重复子串、可重叠k次最长重复子串

    2.2、子串的个数

    1、洛谷 P3809 【模板】后缀排序 (最长公共前缀)

    参考这里,以后求后缀数组都用倍增法吧。

    输入的字符串以0开始计数,长度为n,在字符串末尾加个比输入字符都小的字符使其长度变为n+1。

    求得的height数组可以把数组当成以1开始计数的。

     1 #include<iostream>
     2 #include<sstream>
     3 #include<fstream>
     4 #include<algorithm>
     5 #include<cstring>
     6 #include<iomanip>
     7 #include<cstdlib>
     8 #include<cctype>
     9 #include<vector>
    10 #include<string>
    11 #include<cmath>
    12 #include<ctime>
    13 #include<stack>
    14 #include<queue>
    15 #include<map>
    16 #include<set>
    17 #define mem(a,b) memset(a,b,sizeof(a))
    18 #define random(a,b) (rand()%(b-a+1)+a)
    19 #define ll long long
    20 #define ull unsigned long long
    21 #define e 2.71828182
    22 #define Pi acos(-1.0)
    23 #define ls(rt) (rt<<1)
    24 #define rs(rt) (rt<<1|1)
    25 #define lowbit(x) (x&(-x))
    26 using namespace std;
    27 const int MAXN=1e6+5;
    28 char str[MAXN];
    29 int n,m;
    30 int sa[MAXN],ra[MAXN],height[MAXN];
    31 int wa[MAXN],wb[MAXN],wc[MAXN],wd[MAXN];
    32 int read()
    33 {
    34     int s=1,x=0;
    35     char ch=getchar();
    36     while(!isdigit(ch)) {if(ch=='-') s=-1;ch=getchar();}
    37     while(isdigit(ch)) {x=10*x+ch-'0';ch=getchar();}
    38     return x*s;
    39 }
    40 void GetSaRa(int n,int m)//倍增法 nlogn 
    41 {
    42     int i,j,p,*x=wa,*y=wb,*t;
    43     for(int i=0;i<m;++i) wd[i]=0;
    44     for(int i=0;i<n;++i) wd[x[i]=str[i]]++;
    45     for(int i=1;i<m;++i) wd[i]+=wd[i-1];
    46     for(int i=n-1;i>=0;--i) sa[--wd[x[i]]]=i;
    47     for(j=1,p=1;p<n;j*=2,m=p)
    48     {
    49         for(p=0,i=n-j;i<n;++i) y[p++]=i;
    50         for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;
    51         for(i=0;i<n;++i) wc[i]=x[y[i]];
    52         for(i=0;i<m;++i) wd[i]=0;
    53         for(i=0;i<n;++i) wd[wc[i]]++;
    54         for(i=1;i<m;++i) wd[i]+=wd[i-1];
    55         for(i=n-1;i>=0;--i) sa[--wd[wc[i]]]=y[i];
    56         for(swap(x,y),p=1,x[sa[0]]=0,i=1;i<n;++i)
    57         x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;
    58     }
    59 }
    60 void GetHeight(int n)
    61 {
    62     int i,j,k=0;
    63     for(i=1;i<=n;++i) ra[sa[i]]=i;
    64     for(i=0;i<n;height[ra[i++]]=k)
    65     for(k?k--:0,j=sa[ra[i]-1];str[i+k]==str[j+k];++k);
    66 }
    67 int main()
    68 {
    69     scanf("%s",str);
    70     n=strlen(str);
    71     str[n]='0';m=200;
    72     GetSaRa(n+1,m);
    73     //GetHeight(n); 
    74     //for(int i=0;i<=n;++i) cout<<sa[i]<<' ';cout<<endl; //含末尾加的字符 
    75     for(int i=1;i<=n;++i) cout<<sa[i]+1<<' ';cout<<endl;//不含末尾加的字符 
    76     //for(int i=0;i<n;++i) cout<<ra[i]<<' ';cout<<endl;
    77     //for(int i=2;i<=n;++i)cout<<height[i]<<' ';cout<<endl;    
    78 }
    View Code

    2、HDU4691 Front compression

    后缀数组求单个字符串中不同子串任意两个的最长公共前缀。利用height数组和ST表。

    详见代码。

      1 #include<iostream>
      2 #include<sstream>
      3 #include<fstream>
      4 #include<algorithm>
      5 #include<cstring>
      6 #include<iomanip>
      7 #include<cstdlib>
      8 #include<cctype>
      9 #include<vector>
     10 #include<string>
     11 #include<cmath>
     12 #include<ctime>
     13 #include<stack>
     14 #include<queue>
     15 #include<map>
     16 #include<set>
     17 #define mem(a,b) memset(a,b,sizeof(a))
     18 #define random(a,b) (rand()%(b-a+1)+a)
     19 #define ll long long
     20 #define ull unsigned long long
     21 #define e 2.71828182
     22 #define Pi acos(-1.0)
     23 #define ls(rt) (rt<<1)
     24 #define rs(rt) (rt<<1|1)
     25 #define lowbit(x) (x&(-x))
     26 using namespace std;
     27 const int MAXN=1e5+5;
     28 int n,m;
     29 char str[MAXN];
     30 int sa[MAXN],ra[MAXN],height[MAXN];
     31 int wa[MAXN],wb[MAXN],wc[MAXN],wd[MAXN];
     32 int read()
     33 {
     34     int s=1,x=0;
     35     char ch=getchar();
     36     while(!isdigit(ch)) {if(ch=='-') s=-1;ch=getchar();}
     37     while(isdigit(ch)) {x=10*x+ch-'0';ch=getchar();}
     38     return x*s;
     39 }
     40 void GetSaRa(int n,int m)//倍增法 nlogn 
     41 {
     42     int i,j,p,*x=wa,*y=wb,*t;
     43     for(int i=0;i<m;++i) wd[i]=0;
     44     for(int i=0;i<n;++i) wd[x[i]=str[i]]++;
     45     for(int i=1;i<m;++i) wd[i]+=wd[i-1];
     46     for(int i=n-1;i>=0;--i) sa[--wd[x[i]]]=i;
     47     for(j=1,p=1;p<n;j*=2,m=p)
     48     {
     49         for(p=0,i=n-j;i<n;++i) y[p++]=i;
     50         for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;
     51         for(i=0;i<n;++i) wc[i]=x[y[i]];
     52         for(i=0;i<m;++i) wd[i]=0;
     53         for(i=0;i<n;++i) wd[wc[i]]++;
     54         for(i=1;i<m;++i) wd[i]+=wd[i-1];
     55         for(i=n-1;i>=0;--i) sa[--wd[wc[i]]]=y[i];
     56         for(swap(x,y),p=1,x[sa[0]]=0,i=1;i<n;++i)
     57         x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;
     58     }
     59 }
     60 int dp[MAXN][20];
     61 void ST(int n)
     62 {
     63     for(int i=1;i<=n;++i) dp[i][0]=height[i];
     64     for(int j=1;(1<<j)<=n;++j)
     65     for(int i=1;i+(1<<j)-1<=n;++i)
     66     dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);    
     67 } 
     68 int RMQ(int l,int r)
     69 {
     70     int k=0;
     71     while((1<<(k+1))<=r-l+1) ++k;
     72     return min(dp[l][k],dp[r-(1<<k)+1][k]);
     73 }
     74 void GetHeight(int n)
     75 {
     76     int i,j,k=0;
     77     for(i=1;i<=n;++i) ra[sa[i]]=i;
     78     for(i=0;i<n;height[ra[i++]]=k)
     79     for(k?k--:0,j=sa[ra[i]-1];str[i+k]==str[j+k];++k);
     80 } 
     81 int main()
     82 {
     83     int t,l1,r1,l2,r2;
     84     while(~scanf("%s",str))
     85     {
     86         n=strlen(str);str[n]='0';m=233;
     87         GetSaRa(n+1,m);GetHeight(n);
     88         ST(n);
     89         scanf("%d",&t);
     90         ll input=t,output=2*t+1;//先把换行、空格加上 
     91         scanf("%d%d",&l1,&r1);
     92         input+=(r1-l1);
     93         output+=(r1-l1);//第一个肯定不能压缩 
     94         t--;
     95         while(t--)
     96         {
     97             scanf("%d%d",&l2,&r2);
     98             input+=r2-l2;
     99             int tmp;
    100             if(l2==l1) tmp=min(r1-l1,r2-l2);
    101             else
    102             {
    103                 int k1=ra[l1],k2=ra[l2];
    104                 if(k1>k2) swap(k1,k2);
    105                 tmp=RMQ(k1+1,k2);
    106             }
    107             //因为这儿的后缀不是完整的,取短的,这里的tmp是两个子串的最长公共前缀长度 
    108             tmp=min(tmp,min(r1-l1,r2-l2));
    109             if(!tmp) output++;
    110             else output+=(int)(log10(tmp)+1);//位数,题目规定一个数字一个字节 
    111             output+=(r2-l2-tmp);//剩下的不能复制的
    112             l1=l2,r1=r2; 
    113         }
    114         cout<<input<<' '<<output<<endl;
    115     }
    116 }
    View Code

    3、POJ1743 Musical Theme 

    后缀数组求不可重叠最长重复子串。二分+sa数组+height数组。

    这题有个坑,求出差分数组后,用来二分的判断函数里,详细见代码。

    关于后缀数组中height数组的分组,这个讲的蛮好的:http://www.mamicode.com/info-detail-1057844.html

    i从1到N遍历,通过height[i]>=k将i分开,即将后缀分成若干组,每组中的后缀的公共前缀长度均大于等于k,且可以肯定组A中的某后缀t1和组B中的某后缀t2的公共前缀长度小于M

    然后就是二分牛批好吧。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int MAXN=2e4+5;
     4 int str[MAXN];
     5 int sa[MAXN],ra[MAXN],height[MAXN];
     6 int wa[MAXN],wb[MAXN],wc[MAXN],wd[MAXN];
     7 int read()
     8 {
     9     int s=1,x=0;
    10     char ch=getchar();
    11     while(!isdigit(ch)) {if(ch=='-') s=-1;ch=getchar();}
    12     while(isdigit(ch)) {x=10*x+ch-'0';ch=getchar();}
    13     return x*s;
    14 }
    15 void GetSaRa(int n,int m)//倍增法 nlogn 
    16 {
    17     int i,j,p,*x=wa,*y=wb,*t;
    18     for(int i=0;i<m;++i) wd[i]=0;
    19     for(int i=0;i<n;++i) wd[x[i]=str[i]]++;
    20     for(int i=1;i<m;++i) wd[i]+=wd[i-1];
    21     for(int i=n-1;i>=0;--i) sa[--wd[x[i]]]=i;
    22     for(j=1,p=1;p<n;j*=2,m=p)
    23     {
    24         for(p=0,i=n-j;i<n;++i) y[p++]=i;
    25         for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;
    26         for(i=0;i<n;++i) wc[i]=x[y[i]];
    27         for(i=0;i<m;++i) wd[i]=0;
    28         for(i=0;i<n;++i) wd[wc[i]]++;
    29         for(i=1;i<m;++i) wd[i]+=wd[i-1];
    30         for(i=n-1;i>=0;--i) sa[--wd[wc[i]]]=y[i];
    31         for(swap(x,y),p=1,x[sa[0]]=0,i=1;i<n;++i)
    32         x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;
    33     }
    34 }
    35 void GetHeight(int n)
    36 {
    37     int i,j,k=0;
    38     for(i=1;i<=n;++i) ra[sa[i]]=i;
    39     for(i=0;i<n;height[ra[i++]]=k)
    40     for(k?k--:0,j=sa[ra[i]-1];str[i+k]==str[j+k];++k);
    41 }
    42 bool C(int n,int k)//差分数组中是否存在两个长度为k的子串是相同的 
    43 {
    44     int maxi=-1,mini=1<<30;//下标 
    45     for(int i=1;i<=n;++i)
    46     {
    47         if(height[i]<k)//分组
    48         maxi=sa[i],mini=sa[i];
    49         else//组内是否有相同长度超过k的子串且距离大于k 
    50         {
    51             maxi=max(maxi,max(sa[i],sa[i-1]));
    52             mini=min(mini,min(sa[i],sa[i-1]));
    53             //这里为什么不取等于,明明等于也是可以满足的
    54             //其实这里求得的满足条件的是差分数组满足而不是原数组满足
    55             //最后的结果还要加1就是这么来的 
    56             if(maxi-mini>k) return true;
    57         }
    58     }
    59     return false;
    60 }
    61 int main()
    62 {
    63     //freopen("1.txt","r",stdin);
    64     int n,x,tmp;
    65     while(scanf("%d",&n),n)
    66     {
    67         x=read();tmp=x;
    68         for(int i=1;i<n;++i)
    69         {
    70             x=read();
    71             str[i-1]=x-tmp+100;//差分,避免桶排序中出现负的下标多加个100 
    72             tmp=x; 
    73         }
    74         str[n-1]=0;n--;
    75         GetSaRa(n+1,300);
    76         GetHeight(n);
    77     //    for(int i=0;i<n;++i) cout<<str[i]<<' ';cout<<endl;
    78     //    for(int i=1;i<=n;++i) cout<<height[i]<<' ';cout<<endl;
    79         if(!C(n,4))
    80         {
    81             cout<<0<<endl;
    82             continue;
    83         }
    84         int l=0,r=n,mid;
    85         while(l<r)
    86         {
    87             mid=(l+r+1)>>1;
    88             if(C(n,mid)) l=mid;
    89             else r=mid-1;    
    90         } 
    91         cout<<l+1<<endl;
    92     }
    93     return 0;
    94 }
    View Code

    懂了一点二分后改成:(以后二分全搞成开区间哈哈哈贼爽)

     主要是这个解的上下界挺麻烦确定的,就搞成开区间吧,数值随便,保证开区间就行,输出l还是r还是别的什么找一组输出试试就行了(一般是l,这里加1是因为题意)。

    4、POJ3261 Milk Patterns

    和上题差不多,二分答案,将后缀分成若干组。这里要判断的是有没有一个组的后缀个数不小于k。上题相当于k=2,并且上题不可重叠,这题可重叠。

     1 #include<iostream>
     2 #include<algorithm>
     3 using namespace std;
     4 const int MAXN=2e4+5;
     5 const int MAXM=1e6+5;
     6 int n,k;
     7 int str[MAXN];
     8 int sa[MAXN],ra[MAXN],height[MAXN];
     9 int wa[MAXN],wb[MAXN],wc[MAXN],wd[MAXN];
    10 int read()
    11 {
    12     int s=1,x=0;
    13     char ch=getchar();
    14     while(!isdigit(ch)) {if(ch=='-') s=-1;ch=getchar();}
    15     while(isdigit(ch)) {x=10*x+ch-'0';ch=getchar();}
    16     return x*s;
    17 }
    18 void GetSaRa(int n,int m)//倍增法 nlogn 
    19 {
    20     int i,j,p,*x=wa,*y=wb,*t;
    21     for(int i=0;i<m;++i) wd[i]=0;
    22     for(int i=0;i<n;++i) wd[x[i]=str[i]]++;
    23     for(int i=1;i<m;++i) wd[i]+=wd[i-1];
    24     for(int i=n-1;i>=0;--i) sa[--wd[x[i]]]=i;
    25     for(j=1,p=1;p<n;j*=2,m=p)
    26     {
    27         for(p=0,i=n-j;i<n;++i) y[p++]=i;
    28         for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;
    29         for(i=0;i<n;++i) wc[i]=x[y[i]];
    30         for(i=0;i<m;++i) wd[i]=0;
    31         for(i=0;i<n;++i) wd[wc[i]]++;
    32         for(i=1;i<m;++i) wd[i]+=wd[i-1];
    33         for(i=n-1;i>=0;--i) sa[--wd[wc[i]]]=y[i];
    34         for(swap(x,y),p=1,x[sa[0]]=0,i=1;i<n;++i)
    35         x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;
    36     }
    37 }
    38 void GetHeight(int n)
    39 {
    40     int i,j,k=0;
    41     for(i=1;i<=n;++i) ra[sa[i]]=i;
    42     for(i=0;i<n;height[ra[i++]]=k)
    43     for(k?k--:0,j=sa[ra[i]-1];str[i+k]==str[j+k];++k);
    44 }
    45 bool C(int len)//可重叠k次的最长重复子串长度为len时是否存在 
    46 {
    47     int num=1;//一组中公共前缀长度大于等于len的字串有多少个 
    48     //height[1]=0 
    49     for(int i=2;i<=n;++i)
    50     {
    51         if(height[i]>=len)
    52         {
    53             num++;
    54             if(num>=k) return true;
    55         }
    56         else num=1;    
    57     }
    58     return false;
    59 }
    60 int main()
    61 {
    62     n=read(),k=read();
    63     int maxm=-1;
    64     for(int i=0;i<n;++i) str[i]=read(),maxm=max(maxm,str[i]);
    65     str[n]=0;
    66     GetSaRa(n+1,maxm+5);
    67     GetHeight(n);
    68     int l=-1,r=n+1;
    69     while(l+1!=r)
    70     {
    71         int mid=(l+r)>>1;
    72         if(C(mid)) l=mid;
    73         else r=mid;
    74     }
    75     cout<<l<<endl;    
    76 }
    View Code

    5、SPOJ Distinct Substrings (VJ)

    每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。将所有的后缀按照suffix(sa[1]),suffix(sa[2])、suffix(sa[3]即排名从小到大的顺序计算,对于每一次新加进来的后缀suffix(sa[k]),将产生n-sa[k]个新的前缀(对于长度为k的后缀来说,能产生k个长度不一的子串,n-s[k]求的即是该后缀的长度,此处n指的是原字符串长度),但是其中有height[k]个前缀是和suffix(sa[k-1])重复的,故还要减去height[k]。总的答案加起来即可。

     1 //CSDN博客:https://blog.csdn.net/qq_40889820 
     2 #include<bits/stdc++.h>
     3 using namespace std;
     4 const int MAXN=1e3+5;
     5 char str[MAXN];
     6 int sa[MAXN],ra[MAXN],height[MAXN];
     7 int wa[MAXN],wb[MAXN],wc[MAXN],wd[MAXN];
     8 int read()
     9 {
    10     int s=1,x=0;
    11     char ch=getchar();
    12     while(!isdigit(ch)) {if(ch=='-') s=-1;ch=getchar();}
    13     while(isdigit(ch)) {x=10*x+ch-'0';ch=getchar();}
    14     return x*s;
    15 }
    16 void GetSaRa(int n,int m)//倍增法 nlogn 
    17 {
    18     int i,j,p,*x=wa,*y=wb,*t;
    19     for(int i=0;i<m;++i) wd[i]=0;
    20     for(int i=0;i<n;++i) wd[x[i]=str[i]]++;
    21     for(int i=1;i<m;++i) wd[i]+=wd[i-1];
    22     for(int i=n-1;i>=0;--i) sa[--wd[x[i]]]=i;
    23     for(j=1,p=1;p<n;j*=2,m=p)
    24     {
    25         for(p=0,i=n-j;i<n;++i) y[p++]=i;
    26         for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;
    27         for(i=0;i<n;++i) wc[i]=x[y[i]];
    28         for(i=0;i<m;++i) wd[i]=0;
    29         for(i=0;i<n;++i) wd[wc[i]]++;
    30         for(i=1;i<m;++i) wd[i]+=wd[i-1];
    31         for(i=n-1;i>=0;--i) sa[--wd[wc[i]]]=y[i];
    32         for(swap(x,y),p=1,x[sa[0]]=0,i=1;i<n;++i)
    33         x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;
    34     }
    35 }
    36 void GetHeight(int n)
    37 {
    38     int i,j,k=0;
    39     for(i=1;i<=n;++i) ra[sa[i]]=i;
    40     for(i=0;i<n;height[ra[i++]]=k)
    41     for(k?k--:0,j=sa[ra[i]-1];str[i+k]==str[j+k];++k);
    42 }
    43 int main()
    44 {
    45     int T=read();
    46     while(T--)
    47     {
    48         scanf("%s",str);
    49         int n=strlen(str);
    50         str[n]=0;
    51         GetSaRa(n+1,200);
    52         GetHeight(n);
    53         int ans=0;
    54         for(int i=1;i<=n;++i)
    55         ans+=n-sa[i]-height[i];
    56         cout<<ans<<endl;
    57     }
    58 }
    View Code

    哈希做下:(用set判重没过超时了,改成哈希表过了)

     1 #include<bits/stdc++.h>
     2 #define ull unsigned long long 
     3 using namespace std;
     4 const int MAXN=1e3+5;
     5 const int Base=23333;
     6 const int mod=14937;
     7 ull Hash[MAXN],power[MAXN];
     8 char str[MAXN];
     9 vector<ull> HashTable[mod];
    10 void add_edge(ull h)
    11 {
    12     int pos=h%mod;
    13     HashTable[pos].push_back(h);
    14 }
    15 int find(ull h)
    16 {
    17     int pos=h%mod;
    18     for(int i=0;i<HashTable[pos].size();++i)
    19     if(HashTable[pos][i]==h) return 1;
    20     return 0;
    21 }
    22 int main()
    23 {
    24     int T;
    25     scanf("%d",&T);power[0]=1;
    26     for(int i=1;i<=MAXN;++i) power[i]=power[i-1]*Base;
    27     while(T--)
    28     {
    29         scanf("%s",str+1);
    30         Hash[0]=0;
    31         for(int i=0;i<mod;++i) HashTable[i].clear();
    32         int len=strlen(str+1);
    33         int ans=0;
    34         for(int i=1;i<=len;++i) Hash[i]=Hash[i-1]*Base+str[i];
    35         for(int i=1;i<=len;++i)//length
    36         {
    37             for(int j=1;j<=len-i+1;++j)//left
    38             {   //[j,j+i-1]
    39                 ull tmp=Hash[i+j-1]-Hash[j-1]*power[i];
    40                 if(!find(tmp)) 
    41                 {
    42                     ans++;add_edge(tmp);
    43                 }
    44             }
    45         }
    46         cout<<ans<<endl;
    47     }
    48 }
    View Code
  • 相关阅读:
    SpringBoot | Thymeleaf | 局部更新
    面试 | 冒泡排序优化
    JSP && Servlet | AXIS 0配置 入门
    155. 最小栈
    idea | 命名空间改过后重新导入项目方法
    Java | 基础归纳 | Map.Entry<String, String>
    08_Azkaban案例实践1_Command单一job示例
    07_Azkaban工作流调度器简介及其安装
    06_工作流调度器概述
    05_ Flume多级Agent之间串联案例
  • 原文地址:https://www.cnblogs.com/wangzhebufangqi/p/11317446.html
Copyright © 2020-2023  润新知