后缀数组习题集

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

 

转载于:https://www.cnblogs.com/wangzhebufangqi/p/11317446.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值