后缀数组题型讲解

合集下载

atcoder beginner contest 后缀数组例题

atcoder beginner contest 后缀数组例题

atcoder beginner contest 后缀数组例题以下是一道与后缀数组相关的AtCoder Beginner Contest上的例题解析:问题描述:给定一串长度为 N(1 ≤ N ≤ 10^5)的字符串 S,求字符串 S 的所有后缀排序后的字符串列表。

解题思路:这道题可以使用后缀数组来解决。

后缀数组是一个字符串的所有后缀按照字典序排序后的数组。

首先,我们可以将字符串 S 的所有后缀存到一个数组中,并对这个数组进行排序。

为了实现这一步,我们可以使用一个结构体来存储每个后缀的起始索引和后缀本身。

然后,我们可以使用 C++ 中的 sort 函数对该数组进行排序,排序的比较函数为根据后缀字符串的字典序大小。

接下来,我们只需要遍历排序后的数组,并将每个后缀的后缀字符串输出即可。

下面是一个使用C++实现的解法示例:```cpp#include <iostream>#include <algorithm>#include <vector>#include <string>using namespace std;struct Suffix {int index;string suffix;};bool compareSuffix(Suffix a, Suffix b) {return a.suffix < b.suffix;}vector<string> getSuffixArray(string S) {int N = S.size();vector<string> suffixArray;vector<Suffix> suffixes;for (int i = 0; i < N; i++) {suffixes.push_back({i, S.substr(i)});}sort(suffixes.begin(), suffixes.end(), compareSuffix); for (Suffix suffix : suffixes) {suffixArray.push_back(suffix.suffix);}return suffixArray;}int main() {string S;cin >> S;vector<string> suffixArray = getSuffixArray(S);for (string suffix : suffixArray) {cout << suffix << endl; // 输出每个后缀字符串}return 0;}```这个解法的时间复杂度为O(NlogN),其中N 是字符串的长度。

【字符串】后缀数组

【字符串】后缀数组

【字符串】后缀数组后缀排序倍增算法n字符串的长度。

m当前后缀(离散化后)的值域。

对于char可以跳过离散化,初值取128即可,对于int要离散化,初值取n即可,初值要保证覆盖整个值域。

sa[i]排名为i的后缀的起始位置。

rk[i]起始位置为i的后缀的排名。

验证:const int MAXN = 1000000 + 10;int n, m, ct[MAXN], tp[MAXN];int sa[MAXN], rk[MAXN], ht[MAXN];void RadixSort() {for(int i = 0; i <= m; ++i)ct[i] = 0;for(int i = 1; i <= n; ++i)++ct[rk[i]];for(int i = 1; i <= m; ++i)ct[i] += ct[i - 1];for(int i = n; i >= 1; --i)sa[ct[rk[tp[i]]]--] = tp[i];}bool Compare(int i, int j, int l) {if(tp[sa[i]] == tp[sa[j]]) {if(sa[i] + l <= n && sa[j] + l <= n) {if(tp[sa[i] + l] == tp[sa[j] + l])return 1;}}return 0;}void SuffixSort(char *s) {n = strlen(s + 1), m = 128;for(int i = 1; i <= n; ++i) {rk[i] = s[i];tp[i] = i;}RadixSort();for(int l = 1;; l <<= 1) {m = 0;for(int i = n - l + 1; i <= n; ++i)tp[++m] = i;for(int i = 1; i <= n; ++i) {if(sa[i] > l)tp[++m] = sa[i] - l;}RadixSort();swap(tp, rk);m = 1;rk[sa[1]] = 1;for(int i = 2; i <= n; ++i) {if(Compare(i - 1, i, l) == 0)++m;rk[sa[i]] = m;}if(m == n)break;}}最⼩循环表⽰把字符串S循环移动,找字典序最⼩的那个表⽰。

后缀数组最详细讲解

后缀数组最详细讲解

后缀数组最详细讲解转载⾃后缀数组最详细(maybe)讲解后缀数组这个东西真的是神仙操作……但是这个⽐较神仙的东西在⽹上的讲解⼀般都仅限于思想⽽不是代码,⽽且这个东西开⼀堆数组,很多初学者写代码的时候很容易发⽣歧义理解,所以这⾥给出⼀个⽐较详细的讲解。

笔者⾃⼰也是和后缀数组硬刚了⼀个上午外加⼀个中午才理解的板⼦。

本⼈版权意识薄弱,如有侵权现象请联系博主邮箱xmzl200201@参考⽂献:以下是不认识的dalao们:特别感谢以下的两位dalao,写的特别好,打call什么是后缀数组我们先看⼏条定义:⼦串在字符串s中,取任意i<=j,那么在s中截取从i到j的这⼀段就叫做s的⼀个⼦串后缀后缀就是从字符串的某个位置i到字符串末尾的⼦串,我们定义以s的第i个字符为第⼀个元素的后缀为suff(i)后缀数组把s的每个后缀按照字典序排序,后缀数组sa[i]就表⽰排名为i的后缀的起始位置的下标⽽它的映射数组rk[i]就表⽰起始位置的下标为i的后缀的排名简单来说,sa表⽰排名为i的是啥,rk表⽰第i个的排名是啥⼀定要记牢这些数组的意思,后⾯看代码的时候如果记不牢的话就绝对看不懂后缀数组的思想先说最暴⼒的情况,快排(n log n)每个后缀,但是这是字符串,所以⽐较任意两个后缀的复杂度其实是O(n),这样⼀来就是接近O(n^2 log n)的复杂度,数据⼤了肯定是不⾏的,所以我们这⾥有两个优化。

ps:本⽂中的^表⽰平⽅⽽不是异或倍增⾸先读⼊字符串之后我们现根据单个字符排序,当然也可以理解为先按照每个后缀的第⼀个字符排序。

对于每个字符,我们按照字典序给⼀个排名(当然可以并列),这⾥称作关键字。

接下来我们再把相邻的两个关键字合并到⼀起,就相当于根据每⼀个后缀的前两个字符进⾏排序。

想想看,这样就是以第⼀个字符(也就是⾃⼰本⾝)的排名为第⼀关键字,以第⼆个字符的排名为第⼆关键字,把组成的新数排完序之后再次标号。

没有第⼆关键字的补零。

后缀数组代码详解

后缀数组代码详解

后缀数组代码详解直接解释代码#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define N 100001char ch[N];int n,k,a[N],v[N],p,q=1,sa[2][N],rk[2][N],h[N];void mul(int *sa,int *rk,int *SA,int *RK)//sa,rk是上⼀轮基数排序的结果,SA,RK是本轮结束后基数排序的结果// 把后缀从前往后编号,sa[i]排第i名(排名即字典序⼤⼩)的后缀是谁,rk[i]i号后缀的排名是多少{for(int i=1;i<=n;i++) v[rk[sa[i]]]=i;//v⽤另⼀种⽅法统计了前缀和,i是排名,v[j]=i为排名为1——j的数⼀共有i个for(int i=n;i;i--)if(sa[i]>k) //只有sa[i]>k,i才能既是sa[i]的第⼀关键字排名,⼜是sa[i]-k的第⼆关键字排名SA[v[rk[sa[i]-k]]--]=sa[i]-k;//倍增之后新的sa数组for(int i=n-k+1;i<=n;i++) //n-k之后的⼆元组⽆第⼆关键字,排在所属前缀和范围的最前⾯SA[v[rk[i]]--]=i;for(int i=1;i<=n;i++) //倍增之后新的rk数组RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i]]!=rk[SA[i-1]]||rk[SA[i]+k]!=rk[SA[i-1]+k]);//对⽐两个关键字}void presa(){for(int i=1;i<=n;i++) v[a[i]]++;//相同字母的个数(相同的后缀起始位置)for(int i=1;i<=26;i++) v[i]+=v[i-1];//前缀和,为设定初始排名(即只对⼀个字母的排名)做准备for(int i=1;i<=n;i++)sa[p][v[a[i]]--]=i;//有了前缀和数组v后,数i的排名便在v[i-1]+1——v[i]之间,实际上v[i]-v[i-1]个数的排名相等,这⾥不设为相等for(int i=1;i<=n;i++)rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]);//sa数组与rk数组相反,sa[i]=j表⽰排名为i的后缀起始位置为j,rk[i]=j表⽰起始位置为i的后缀排名为jfor(k=1;k<n;k<<=1,swap(p,q))//真正开始倍增排序//p所代表的的是上⼀轮信息,q代表的是要进⾏的本轮排序信息//上⼀轮基数排序的结果,是本轮基数排序的要利⽤的信息,所以p,q交换mul(sa[p],rk[p],sa[q],rk[q]);//求height数组://height[i]:排名为i的后缀和排名为i-1的后缀的最长公共前缀//设h[i]表⽰i和rk[i]-1的最长公共前缀,即i号后缀和排在i号后缀前⼀名的后缀的最长公共前缀//那么有⼀个性质:h[i]>=h[i-1]-1//注:字符串中,i号后缀的顺序在i-1号后缀后⾯,排名就不⼀定了,后⾯的排在XX哪⼉均指排名//证明://若h[i-1]=1,显然成⽴//若h[i-1]>1,设排名为rk[i-1]-1的后缀为k号后缀,即后缀k排在后缀i-1的前⼀位//那么h[i-1]=后缀i-1和后缀k的最长公共前缀,显然他们⾄少前两个字符相同//i号后缀即为i-1号后缀去掉第⼀个字母,k+1号后缀即为k号后缀去掉第⼀个字母//显然k+1号后缀⼀定排在i号后缀的前⾯,否则k号后缀就不会排在i-1号后缀的前⾯//若k+1号后缀恰好排在i号后缀的前⼀名,显然成⽴//否则,设排在i好后缀的前⼀名是L号后缀,L号后缀⼀定在k+1号后缀的后⾯//L号后缀和i号后缀的最长公共前缀>=k+1号后缀和i号后缀的最长公共前缀//因为i号后缀和k+1号后缀的最长公共前缀⼀定是在L的前缀,否则L不会排名在i和k+1的中间;L号后⾯还会有可能继续与i号匹配 //所以h[i]>=h[i-1]-1,即i号后缀和排在i号前⼀名的后缀的最长公共前缀>=i-1号后缀和排在i-1号前⼀名的后缀的最长公共前缀-1 for(int i=1,k=0;i<=n;i++){int j=sa[p][rk[p][i]-1];//p:虽然倍增过程中新的⼀轮排序信息存在q⾥,但结束后还要执⾏swap,所以⽤p⾥的信息while(a[i+k]==a[j+k]) k++;h[rk[p][i]]=k;if(k) k--;//这⾥的h数组指的是上⾯说的height数组,上⾯的h数组的信息是⽤来辅助计算height的,可以不⽤存}}int main(){scanf("%s",ch+1);n=strlen(ch+1);for(int i=1;i<=n;i++) a[i]=ch[i]-'a'+1;//字符串转化为数字presa();for(int i=1;i<=n;i++) printf("%d ",sa[p][i]);puts(""); for(int i=2;i<=n;i++) printf("%d ",h[i]);}。

后缀树与后缀数组

后缀树与后缀数组

• 显然, LCP(Suffix(i+1), Suffix(j+1)) = max(h[k]-1,0);
i i+1
j j+1
• 设i+1在sa中位置为t,sa[t+1] = p 即h[t] = LCP(suffix(i+1),suffix(p)) • 由suffix(i) < suffix (j) => suffix(i+1) < suffix(j+1) • 而suffix(p) 在sa数组中的位置紧贴着suffix(i+1),所以有 suffix(i+1) < suffix(p) <= suffix(j+1) • 而LCP(suffix(i+1),suffix(j+1)) = max(h[k]-1,0) 下标:1 2 3 4 5 Sa数组
– 当i超过字符串的长度,可以认为s[i] = -oo。
• 后缀:指从某个位置i开始到整个串结束的一个 特殊子串。字符串S的从i个字符开始的后缀记为 Suffix(i)。
– 显然,Suffix(i) = S[i..len(S)],记为S(i)
• 字符串的大小比较:例如串S与串T,从小到大 枚举i,如果s[i] < t[i] => S < T, 如果s[i] > t[i] => S > T。 两个串完全匹配则S== T
• 名次数组:名次数组Rank[i]保存的是 Suffix(i) 在所有后缀中从小到大排 列的“名次”。 可以视为大小
– 简单来说,名次数组就是问“你排第几”
• 显然,两者只要知道一个,就可以推出另外 一个
下标:1
2

后缀数组板子

后缀数组板子

后缀数组hdu6704题意:查询l-r之间的子串第k次出现的位置一个子串必定是某个后缀的前缀。

排序相邻的后缀他们的前缀一定最相似。

所以全部的一种子串必定是一些排序相邻的后缀的公共前缀。

从l开始的子串,则从rank[l]开始看,两侧height保证大于子串长度,能延伸多长,则证明有多少个这种子串。

我们用ST表维护出height的最小值,然后通过最小值二分即可,边界有些棘手。

然后我们就得到了一个height不小于子串长度的连续区间,这个区间是以原后缀的字典序排序的。

而同时,sa数组下标为排序,值为原串位置。

所以我们对这个区间在sa数组上做主席树,求第k大,即为第k个子串出现的位置#include<bits/stdc++.h>using namespace std;typedef long long ll;const int INF=0x3f3f3f3f;const int N=1e5+5;char s[N];int sa[N],t[N],t2[N],c[N],n,m;//c是一个桶,sa是后缀数组,sa[i]表示排名第i的是第几个串//x表示rank,x[i]表示第i个串的排名,也是第一关键字//y[i]表示第二关键字排名第i的是第几个串int height[N],rk[N],d[N][30];int root[N],cnt;struct node{int l,r,sum;}T[N*40];void build_sa(int m){int i,*x=t,*y=t2;//基数排序for(int i=1;i<=m;i++)c[i]=0;//清空桶for(i=1;i<=n;i++){//一开始的rank为第一个字符的大小,可以用ascii码表示,并加入桶c[x[i]=s[i]]++;}for(i=2;i<=m;i++){//前缀和,c[i]可以表示前面有多少个比他大c[i]+=c[i-1];}for(i=n;i>=1;i--){sa[c[x[i]]--]=i;//更新sa}for(int k=1;k<=n;k<<=1)//通过第一位依次倍增出每一位{int p=0;//直接利用sa数组排序第二关键字for(i=n-k+1;i<=n;i++){y[++p]=i;//第n-k到第n个串没有后面的后缀,排在最前面}for(i=1;i<=n;i++){//加入排名第i的是第j个串,此时这个串就是j-k个串的右半部分,也就是第二关键字的排名直接可以通过sa获得if(sa[i]>k)y[++p]=sa[i]-k;}//基数排序第一关键字for(i=1;i<=m;i++)c[i]=0;for(i=1;i<=n;i++)c[x[y[i]]]++;for(i=2;i<=m;i++)c[i]+=c[i-1];for(i=n;i>=1;i--){sa[c[x[y[i]]]--]=y[i];//通过先把第二关键字大的先搞出来,可以实现双关键字排序}//根据sa和y数组计算新的x数组swap(x,y);p=1;x[sa[1]]=1;//通过sa更新x,其中如果两个串完全相同,那么rank,也就是x也相同for(i=2;i<=n;i++){x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p:++p;}if(p>=n)break;m=p;}}void get_Height(){int i,j,k=0;for(i=1;i<=n;i++){rk[sa[i]]=i;}for(i=1;i<=n;i++){if(rk[i]==1)continue;if(k)k--;j=sa[rk[i]-1];while(s[i+k]==s[j+k]&&(i+k<=n)&&(j+k<=n))k++;height[rk[i]]=k;}}void RMQ_init(){for(int i=1;i<=n;i++)d[i][0]=height[i];for(int j=1;(1<<j)<=n;j++){for(int i=1;i+(1<<j)-1<=n;i++){d[i][j]=min(d[i][j-1],d[i+(1<<(j-1))][j-1]);}}}int RMQ(int L,int R){int k=0;while((1<<(1+k))<=R-L+1) k++;return min(d[L][k],d[R-(1<<k)+1][k]);}int LCP(int x,int y){int l=rk[x],r=rk[y];if(l>r)swap(l,r);if(l==r)return n-sa[l];return RMQ(l+1,r);}int getL(int l,int r){int left=1,right=rk[l],len=r-l+1;int ans=rk[l];while(left<=right){int mi=(left+right)>>1;if(LCP(sa[mi],l)<len)left=mi+1;else{right=mi-1;ans=mi;}}return ans;}int getR(int l,int r){int ans=rk[l];int left=rk[l],right=n,len=r-l+1;while(left<=right){int mi=(left+right)>>1;if(LCP(sa[mi],l)<len){right=mi-1;}else{left=mi+1;ans=mi;}}return ans;}void update(int l,int r,int pre,int &now,int pos) {T[++cnt]=T[pre];now=cnt;T[cnt].sum++;if(l==r)return;int m=(l+r)>>1;if(pos<=m)update(l,m,T[pre].l,T[now].l,pos);else update(m+1,r,T[pre].r,T[now].r,pos);}int query(int l,int r,int pre,int now,int k){if(l==r)return l;int m=(l+r)>>1;int sum=T[T[now].l].sum-T[T[pre].l].sum;if(k<=sum)return query(l,m,T[pre].l,T[now].l,k);else return query(m+1,r,T[pre].r,T[now].r,k-sum);}int main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t,q;m='z'+1;cin>>t;while(t--){cin>>n>>q;cin>>s+1;build_sa('z'+1);get_Height();RMQ_init();cnt=0;for(int i=1;i<=n;i++){update(1,n,root[i-1],root[i],sa[i]);}while(q--){int l,r,k;cin>>l>>r>>k;int L=getL(l,r);int R=getR(l,r);if(R-L+1<k)cout<<-1<<endl;else cout<<query(1,n,root[L-1],root[R],k)<<endl;}}}。

后缀数组入门(一)——后缀排序

后缀数组入门(一)——后缀排序

后缀数组⼊门(⼀)——后缀排序前⾔后缀数组这个东西早就有所⽿闻,但由于很难,学了好⼏遍都没学会。

最近花了挺长⼀段时间去研究了⼀下,总算是勉强学会了⽤倍增法来实现后缀排序(据说还有⼀种更快的DC3法,但是要难得多)。

数组定义⾸先,为⽅便起见,我们⽤后缀i表⽰从下标i开始的后缀。

(相信⼤家都知道后缀是什么的)⾸先,我们需要定义⼏个数组:s:需要进⾏后缀排序的字符串。

SA i:记录排名为i的后缀的位置。

rk i:记录后缀i的排名。

pos i:同样记录排名为i的后缀的位置。

tot i:⽤于基数排序,统计i的排名。

要注意理解这些数组的定义,这样才能明⽩后⾯的内容。

第⼀次操作⾸先,让我们来⼀步步模拟⼀下第⼀次操作。

我们第⼀步是要将每个后缀按照第1个字符进⾏排序。

这应该还是⽐较简单的,不难发现可以初始化得到rk i=s i,pos i=i。

然后我们对其进⾏第⼀次排序。

注意,排序最好⽤O(n)的基数排序,⽤sort的话会多⼀个log。

具体的⼀些关于基数排序的细节可以见下。

关于基数排序后缀排序中的基数排序,其实相当于将⼆元组(rk i,pos i)进⾏排序。

⾸先,第⼀步⾃然是清空tot数组。

加1。

然后,从1到n枚举,将tot rki接下来是⼀遍累加,求出每⼀个元素的排名。

然后从n到1倒序枚举,更新SA数组即可。

接下来的操作接下来⾃然是要对每个后缀前2个字符进⾏排序了。

暴⼒的⽅法就是再重新排序⼀遍。

但实际上,在确定了第1个字符的⼤⼩关系后,我们就不需要如此⿇烦了。

因为后缀i的第2个字符,实际上就是后缀i+k的第1个字符。

因此我们通过第⼀次排序,就可以直接确定第2个字符的⼤⼩关系了。

于是我们就可以重新⽤pos数组将这个⼤⼩关系记录下来,再次排序。

然后就是按照这种⽅法来倍增处理第4个字符、第8个字符、第16个字符... ...重复此操作直⾄所有后缀各不相同即可。

这样的总复杂度就是O(nlogn)的了。

具体实现还是有很多细节的,实在没理解的可以根据代码再研究⼀下。

POJ-3581Sequence(后缀数组)

POJ-3581Sequence(后缀数组)

POJ-3581Sequence(后缀数组)题意:给定N个数字组成的序列A1,A2,...,A n。

其中A1⽐其它数字都⼤。

现在要把这个序列分成三段,并将每段分别反转,求得到的字典序最⼩的序列是什么?要求分得的每段都不为空。

分析:⾸先确定第⼀段的分割位置。

这很好说,由于A1⽐其它数字都⼤,确定第⼀段的分割位置只需要考虑第⼀段就⾜够。

将这个字符串反转之后,求反转后的字符串的字典序最⼩的后缀。

重要的是后⾯的段,这两部分不独⽴。

根据⽩书的讲解,我们可以得到如下的图,这样就清晰了#include <iostream>#include <cstdio>#include <cstring>#include <vector>#include <string>#include <algorithm>using namespace std;const int N = 200005;const int inf = 0x3f3f3f3f;int a[N], n, k;int rev[2 * N], sa[2 * N];int rk[2 * N], tmp[2 * N];bool compare_sa(int i, int j){if (rk[i] != rk[j]) return rk[i] < rk[j];else{int ri = i + k <= n ? rk[i + k] : -1;int rj = j + k <= n ? rk[j + k] : -1;return ri < rj;}}void construct_sa(int s[], int n, int sa[]){for (int i = 0; i <= n; ++i){sa[i] = i;rk[i] = i < n ? s[i] : -inf;}for (k = 1; k <= n; k *= 2){sort(sa, sa + n + 1, compare_sa);tmp[sa[0]] = 0;for (int i = 1; i <= n; ++i){tmp[sa[i]] = tmp[sa[i - 1]] + (compare_sa(sa[i - 1], sa[i]) ? 1 : 0);}for (int i = 0; i <= n; ++i){rk[i] = tmp[i];}}}void solve(){//将a反转,并计算其后缀数组reverse_copy(a, a + n, rev);construct_sa(rev, n, sa);int p1;for (int i = 0; i < n; ++i){p1 = n - sa[i];if (p1 >= 1 && n - p1 >= 2) break;}int m = n - p1;reverse_copy(a + p1, a + n, rev);reverse_copy(a + p1, a + n, rev + m);construct_sa(rev, 2 * m, sa);int p2;for (int i = 0; i <= 2 * m; ++i){p2 = p1 + m - sa[i];if (p2 - p1 >= 1 && n - p2 >= 1) break;}reverse(a, a + p1);reverse(a + p1, a + p2);reverse(a + p2, a + n);for (int i = 0; i < n; ++i) printf("%d\n", a[i]); }int main(){scanf("%d", &n);for (int i = 0; i < n; ++i) scanf("%d", &a[i]); solve();return 0;}Processing math: 100%。

字符串之后缀数组

字符串之后缀数组

后缀数组——处理字符串的有力工具作者:罗穗骞2009年1月【摘要】后缀数组是处理字符串的有力工具。

后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也并不逊色,而且它比后缀树所占用的内存空间小很多。

可以说,在信息学竞赛中后缀数组比后缀树要更为实用。

本文分两部分。

第一部分介绍两种构造后缀数组的方法,重点介绍如何用简洁高效的代码实现,并对两种算法进行了比较。

第二部分介绍后缀数组在各种类型题目中的具体应用。

【关键字】字符串,后缀,后缀数组,名次数组,基数排序,【正文】一、后缀数组的实现本节主要介绍后缀数组的两种实现方法:倍增算法(Doubling Algorithm)和DC3算法(Difference Cover),并对两种算法进行了比较。

可能有的读者会认为这两种算法难以理解,即使理解了也难以用程序实现。

本节针对这个问题,在介绍这两种算法的基础上,还给出了简洁高效的代码。

其中倍增算法只有25行,DC3算法只有40行。

1.1、基本定义子串:字符串S的子串r[i..j],i≤j,表示r串中从i到j这一段,也就是顺次排列r[i],r[i+1],...,r[j]形成的字符串。

后缀:后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。

字符串r的从第i个字符开始的后缀表示为Suffix(i),也就是Suffix(i)=r[i..len(r)]。

大小比较:关于字符串的大小比较,是指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i从1开始顺次比较u[i]和v[i],如果u[i]=v[i]则令i加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v (也就是v<u),比较结束。

如果i>len(u)或者i>len(v)仍比较不出结果,那么若len(u)<len(v)则认为u<v,若len(u)=len(v)则认为u=v,若len(u)>len(v)则u>v。

后缀数组 模板题

后缀数组 模板题

后缀数组模板题后缀数组是一种用于处理字符串的数组,其中每个元素都是原字符串的一个后缀。

在算法和数据结构中,后缀数组常用于解决字符串相关的问题,例如字符串匹配、最长重复子串等。

以下是一个使用后缀数组解决最长重复子串问题的模板题:题目描述:给定两个字符串s和p,找出s中最长也是p中的子串的长度。

如果不存在这样的子串,则返回0。

输入格式:输入的第一行包含一个字符串s,长度不超过100000。

输入的第二行包含一个字符串p,长度不超过100000。

输出格式:输出一个整数,表示s中最长也是p中的子串的长度。

示例:输入:abcdefgabcdfeg输出:4解释:在字符串s和p中,最长的公共子串是"efg",其长度为4。

算法思路:1. 构建后缀数组:首先将字符串s转换为后缀数组SA,可以使用KMP算法或Manacher算法。

2. 构建部分匹配数组PM:对于后缀数组SA中的每个元素,统计以该元素结尾的子串在p中出现的次数,保存在部分匹配数组PM中。

注意,PM的长度应为SA+1,因为我们需要包括空字符串。

3. 查找最长公共子串:遍历后缀数组SA中的每个元素,对于每个元素i,从PM[i]开始向前搜索,找到最长的公共子串的长度。

具体做法是,对于每个元素i,如果PM[i]不为0,则说明以i结尾的子串在p中出现过。

从PM[i]开始向前搜索,找到最长的公共子串的长度。

如果PM[i]为0,则说明以i结尾的子串在p中没有出现过,直接跳过该元素。

4. 返回结果:返回找到的最长公共子串的长度。

最长公共子串问题的后缀数组解法

最长公共子串问题的后缀数组解法

最长公共⼦串问题的后缀数组解法[最长公共⼦串]最长公共⼦串(Longest Common Substring ,简称LCS)问题,是指求给定的⼀组字符串长度最⼤的共有的⼦串的问题。

例如字符串”abcb”,”bca”,”acbc”的LCS就是”bc”。

求多串的LCS,显然穷举法是极端低效的算法。

改进⼀些的算法是⽤⼀个串的每个后缀对其他所有串进⾏部分匹配,⽤KMP算法,时间复杂度为O(N*L^2),其中N为字符串个数,L为每个串的长度。

更优秀的有⼴义后缀树的⽅法,时间可以达到 O(N*L)。

本⽂介绍⼀种基于后缀数组的LCS解法,利⽤⼆分查找技术,时间复杂度可以达到O(N*L*logL)。

[最长公共⼦串问题的后缀数组解法]关于后缀数组的构建⽅法以及Height数组的性质,本⽂不再具体介绍,可以参阅IOI国家集训队2004年论⽂《后缀数组》(许智磊)和IOI国家集训队2009年论⽂《后缀数组——处理字符串的有⼒⼯具》(罗穗骞)。

回顾⼀下后缀数组,SA[i]表⽰排名第i的后缀的位置,Height[i]表⽰后缀SA[i]和SA[i-1]的最长公共前缀(Longest Common Prefix,LCP),简记为Height[i]=LCP(SA[i],SA[i-1])。

连续的⼀段后缀SA[i..j]的最长公共前缀,就是H[i-1..j]的最⼩值,即LCP(SA[i..j])=Min(H[i-1..j])。

求N个串的最长公共⼦串,可以转化为求⼀些后缀的最长公共前缀的最⼤值,这些后缀应分属于N个串。

具体⽅法如下:设N个串分别为S1,S2,S3,…,SN,⾸先建⽴⼀个串S,把这N个串⽤不同的分隔符连接起来。

S=S1[P1]S2[P2]S3…SN-1[PN-1]SN,P1,P2,…PN-1应为不同的N-1个不在字符集中的字符,作为分隔符(后⾯会解释为什么)。

接下来,求出字符串S的后缀数组和Height数组,可以⽤倍增算法,或DC3算法。

后缀数组DC3构造法——详解

后缀数组DC3构造法——详解

后缀数组DC3构造法——详解 学习了后缀数组,顺便把DC3算法也看了⼀下,传说中可以O(n)复杂度求出⽂本串的height,先⽐较⼀下倍增算法和DC3算法好辣。

DC3 倍增法时间复杂度 O(n)(但是常数很⼤) O(nlogn)(常数较⼩)空间复杂度 O(n) O(n) 编程复杂度较⾼ 较低 由于在时间复杂度上DC3的常数⽐较⼤,再加上编程复杂度⽐较⾼,所以在解决问题的时候并不是最优选择。

但是学到了后缀数组还是补充⼀下的好点。

DC3算法的实现: 1:先把⽂本串的后缀串分成两部分,第⼀部分是后缀串i mod 3 == 0, 第⼆部分是i mod 3 != 0,然后先⽤基数排序对第⼆部分后缀串排序(按照前三个字符进⾏排序)。

1int *san = sa+n, *rn = r+n, ta=0, tb=(n+1)/3, tbc=0, i, j, p;2//ta i mod 3==0的个数,tb i mod 3==1的个数, tbc imod3!=0的个数3for (i=0; i<n; i++)4if (i % 3)5 x[tbc ++] = i;67 r[n] = r[n+1] = 0;//在⽂本串后⾯添加两个0,便于处理8 Sort (r+2, x, y, tbc, m);9 Sort (r+1, y, x, tbc, m);10 Sort (r, x, y, tbc, m); 然后把suffix[1]与suffix[2]数组连起来,每三个相邻的字符看做⼀个数,变成这个样⼦:操作代码如下:1 rn[F(y[0])] = 0;2for (i=1, p=1; i<tbc; i++)3 rn[F(y[i])] = c0(r, y[i-1], y[i])?p-1:p++;4//#define F(x) x/3+(x%3==1?0:tb)5//F(x) 求原字符串suffix(i)在新串中的位置如果p>=tbc的话,也就是说只排列前三个字符就可以区分出第⼆部分后缀串的顺序了,否则就要进⾏递归继续对第⼆部分的串进⾏排序。

后缀数组(SA)

后缀数组(SA)

后缀数组(SA)SA 的⽤处(经典题型):求LCP,LCS,本质不同⼦串数出现 xxx 次⼦串,字符串中不重叠地出现⾄少两次的最长串长度最长AA式⼦串,连续的若⼲相同⼦串(AABB式)⾸先说⼀下,本篇博客是本⼈在oi-wiki学习后缀数组是写下的笔记,更详细的证明等见后缀数组主要包含两个数组:sa[ ]和rk[ ]。

sa[i]表⽰所有后缀中字典序第i⼩的那个后缀。

rk[i]表⽰i后缀的字典序排名。

求后缀数组模板提交处:其中倍增求SA的代码如下(使⽤基数排序):#include <iostream>#include <algorithm>#include <cmath>#include <cstdio>#include <cstring>#include <string>#define N 2000010template<typename T> inline void read(T &x) {x = 0; char c = getchar(); bool flag = false;while (isdigit(c)) {if (c == '-') flag = true; c = getchar(); }while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); }if (flag) x = -x;}using namespace std;char s[N];int sa[N], rk[N], oldrk[N], id[N], p, n, w, limi;int bin[N];int main() {scanf("%s", s + 1);n = strlen(s + 1);for (register int i = 1; i <= n; ++i) ++bin[rk[i] = s[i]];for (register int i = 1; i <= 300; ++i) bin[i] += bin[i - 1];for (register int i = n; i > 0; --i) sa[bin[rk[i]]--] = i;limi = 300;//注意是limi不是p,因为for⼀开始时不会执⾏limi = pfor (w = 1; w < n; w <<= 1, limi = p) {//处理idp = 0; //注意p = 0for (register int i = n; i > n - w; --i) id[++p] = i;//注意"i"for (register int i = 1; i <= n; ++i)if (sa[i] > w) id[++p] = sa[i] - w;//注意"sa[i] - w" 注意"sa[i]"//处理samemset(bin, 0, sizeof(bin));for (register int i = 1; i <= n; ++i) ++bin[rk[id[i]]];for (register int i = 1; i <= limi; ++i) bin[i] += bin[i - 1];for (register int i = n; i > 0; --i) sa[bin[rk[id[i]]]--] = id[i];//处理rk(去重)memcpy(oldrk, rk, sizeof(rk));p = 0; //注意p = 0for (register int i = 1; i <= n; ++i)//注意"i - 1" 注意"oldrk"rk[sa[i]] = (oldrk[sa[i]] == oldrk[sa[i - 1]] && oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) ? p : ++p; //注意 + wif (p == n) break;//⼩优化:如果已经排好序了,就不⽤再排了。

字符串--后缀数组的运用

字符串--后缀数组的运用

字符串--后缀数组的运⽤后缀数组的运⽤主要体现在三个数组的使⽤之中1.sa[i]=n;表⽰的是排名是第i名的是第n个后缀2.rank[n]=i;表⽰的是第n个后缀在排序中是排在第⼏位的,它和sa数组是相反的。

3.height[ ]表⽰的是相邻的两个后缀之间的最长的公共前缀。

这三种数组的运⽤是有很多种,先总结两种。

⼀,求出现⾄少k次的最长不重复⼦串这就是height[ ]最典型的运⽤。

⼀般的思路是⼆分总的字符串的长度,来寻找符合条件的height[ ]中的段落,⼀旦出现符合条件的段落就记录下来,同时输出就可以。

例题题意:给出n个字符串,要求你找出⾄少在(n)/2个字符串中出现过的最长字串。

思路;这是⼀道典型的使⽤height[ ]数组的题⽬,这⾥只需要将给出的字符串都合并起来,同时在每⼀个合并的地⽅加上⼀个从来都没有出现过的字符,避免在两个字符串相交的地⽅出现符合条件的字串。

同时在最后合并的最后加上⼀个没有出现过的最⼩的字符。

这是为了,在建⽴三个数组的时候更好操作。

所以这题我们只要对⼀个长度进⾏⼆分,然后在height[ ]数组中分段就好了。

那么我们选择什么长度呢,这⾥只要选择所有给出的串中最长的⼀个,显然这是很符合条件的。

那么下⾯就是代码#include<cstdio>#include<cstring>#include<algorithm>#include<iostream>#include<string>#include<vector>#include<stack>#include<bitset>#include<cstdlib>#include<cmath>#include<set>#include<list>#include<deque>#include<map>#include<queue>using namespace std;typedef long long ll;const double PI = acos(-1.0);const double eps = 1e-6;const int mod =1000000;const int maxn =140110;char s[1100];int str[maxn];int Rank[maxn],height[maxn];int sa[maxn],t[maxn],t2[maxn],c[maxn];int belong[maxn],visit[200];void build_sa(int * s,int n,int m){int i,*x = t,*y = t2;for(i = 0;i < m;i++)c[i] = 0;for(i = 0;i < n;i++)c[ x[i] = s[i] ]++;for(i = 1;i < m;i++)c[i] += c[i-1];for(i = n-1;i >= 0;i--) sa[--c[x[i]]] = i;for(int k = 1;k <= n;k <<= 1){int p = 0;for(i = n - k;i < n;i++) y[p++] = i;for(i = 0;i < n;i++) if(sa[i] >= k) y[p++] = sa[i] - k;for(i = 0;i < m;i++) c[i] = 0;for(i = 0;i < n;i++) c[ x[y[i]] ]++;for(i = 0;i < m;i++) c[i] += c[i-1];for(i = n-1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i];swap(x,y);p = 1; x[sa[0]] = 0;for(i = 1;i < n;i++)x[sa[i]] = y[sa[i-1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p-1:p++;if(p >= n) break;m = p;}}void calheight(int * s,int n){int i,j,k = 0;for(i = 0;i < n;i++)Rank[sa[i]] = i;for(i = 0;i < n;i++){if(k) k--;int j = sa[Rank[i]-1];while(s[i+k] == s[j+k]) k++;height[Rank[i]] = k;}}int Judge(int n,int len,int num){int i,j,k;int cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[0]]]){cnt++;visit[belong[sa[0]]]=1;}for(i=1;i<n;i++){if(height[i]<len){cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}else{if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}if(cnt>=num) return 1;}return 0;}void out(int n,int len,int num){int i,j,cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[0]]]){cnt++;}visit[belong[sa[0]]]=1;for(i=1;i<n;i++){if(height[i]<len){if(cnt>=num){for(j=sa[i-1];j<sa[i-1]+len;j++) printf("%c",str[j]-20);printf("\n");}cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}else{if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}}if(cnt>=num){for(j=sa[n-1];j<sa[n-1]+len;j++)printf("%c",str[j]-20);printf("\n");}}int main(){int n;int flag=1;while(scanf("%d",&n)==1&&n){if(!flag) printf("\n");else flag=0;int i,j,k;int pos=0,cnt=1,left=0,right=0;memset(belong,0,sizeof(belong));for(i=1;i<=n;i++){scanf("%s",s);int num=strlen(s);right=max(right,num);for(j=0;j<num;j++){str[pos+j]=int(s[j])+20;belong[pos+j]=i;}str[pos+num]=cnt++;pos=pos+num+1;}str[pos]=0;build_sa(str,pos+1,150);calheight(str,pos+1);int max_x=0;while(left<=right){int mid=(left+right)>>1;if(Judge(pos+1,mid,n/2+1)){max_x=mid;left=mid+1;}else{right=mid-1;}}//cout<<max_x<<"....."<<endl;if(max_x==0){printf("?\n");}else{out(pos+1,max_x,n/2+1);}}return 0;}2.求出现k次或者是⾄少k次的最长可重叠⼦串这个问题和上⾯的问题就很类似,实际上差别就不会很⼤,上⾯的要求是不能重叠,⽽这⾥则是可以重叠的,那么反⽽这个类型是更加简单的。

字符串匹配(三)----后缀数组算法

字符串匹配(三)----后缀数组算法

字符串匹配(三)----后缀数组算法⼀、什么是后缀数组: 字符串后缀Suffix指的是从字符串的某个位置开始到其末尾的字符串⼦串。

后缀数组 Suffix Array(sa)指的是将某个字符串的所有后缀按字典序排序之后得到的数组,不过数组中不直接保存所有的后缀⼦串,只要记录后缀的起始下标就好了。

⽐如下⾯在下⾯这张图中,sa[8] = 7,表⽰在字典序中排第9的是起始下标为7的后缀⼦串,这⾥还有⼀个⽐较重要的数组rank,rank[i] : sa[i]在所有后缀中的排名,⽐如rk[5]=0,表⽰后缀下标为5的⼦串在后缀数组中排第0个; rank数组与sa数组互为逆运算,rk[sa[i]]=i; 现在假如我们已经求出来了后缀数组,然后直接对已经排好序的后缀数组进⾏⼆分查找,这样就能匹配成功了,下⾯贴出代码:import java.util.Arrays;public class SuffixArrayTest {public static void main(String[] args) {match(); // 得到结果是5}static void match(){String s = "ABABABABB";String p = "BABB";Suff[] sa = getSa(s); // 后缀数组int l = 0;int r = s.length()-1;// ⼆分查找,nlog(m)while(r>=l){int mid = l + ((r-l)>>1);// 居中的后缀Suff midSuff = sa[mid];String suffStr = midSuff.str;int compareRes;// 将后缀和模式串⽐较,O(n);if (suffStr.length()>=p.length()) {compareRes = suffStr.substring(0, p.length()).compareTo(p);}else {compareRes = pareTo(p);}// 相等了输出后缀的起始位置if(compareRes == 0){System.out.println(midSuff.index);break;}else if (compareRes<0) {l = mid + 1;}else {r = mid - 1;}}}/*** 直接对所有后缀排序,因为字符串的⽐较消耗O(N),所以整体为N²log(N)* @param src* @return*/public static Suff[] getSa(String src){int strLength = src.length();// sa 即SuffixArray,后缀数组// sa 是排名到下标的映射,即sa[i]=k说明排名为i的后缀是从k开始的Suff[] suffixArray = new Suff[strLength];for (int i = 0; i < strLength; i++) {String suffI = src.substring(i); //截取后缀suffixArray[i] = new Suff(suffI, i);}Arrays.sort(suffixArray); //依据Suff的⽐较规则进⾏排序return suffixArray;}static class Suff implements Comparable<Suff>{String str; //后缀内容int index; //后缀的起始下标public Suff(String str, int index) {super();this.str = str;this.index = index;}@Overridepublic int compareTo(Suff o2) {return pareTo(o2.str);}@Overridepublic String toString() {return "Suff{"+"str='"+str+"\'"+",index="+index+"}";}}}⼆、倍增法 上⾯求后缀数组的⽅式时间复杂度为n²log(n),⼀般来说,时间复杂度只要达到了n平⽅级别都要想办法降低,于是就有⼀种叫做倍增法的⽅法来求后缀数组,基本思想就是: 1、先将每个字符排序得到sa,rank数组, 2、然后给每个字符增添⼀个字符,这样就变成了两个字符,最后⼀个字符⽆法增添字符,就需要处理好边界问题。

atcoder beginner contest 后缀数组例题

atcoder beginner contest 后缀数组例题

atcoder beginner contest 后缀数组例题AtCoder Beginner Contest - Suffix Array ExampleIntroduction:Suffix arrays are important data structures in string manipulation and analysis. They provide a way to efficiently sort all the suffixes of a given string or array. In this article, we will explore an example problem from the AtCoder Beginner Contest that involves the usage of suffix arrays.Problem Description:You are given a string of lowercase English alphabets, S. Your task is to find the length of the longest common prefix among all the suffixes of S, except for the suffix starting at index 0.Example:Let's understand the problem statement with an example. Consider the string S = "abcaabca". The suffixes of this string are:- abcaabca (index 0)- bcaabca (index 1)- caabca (index 2)- aabca (index 3)- abca (index 4)- bca (index 5)- ca (index 6)- a (index 7)Here, the longest common prefix among all the suffixes, except for the suffix at index 0, is "a". Hence, the length of the longest common prefix is 1.Solution Approach:To solve this problem efficiently, we can make use of the concept of suffix arrays. First, we generate the suffix array for the given string S. Then, we compare adjacent suffixes to find the longest common prefix.Let's walk through the steps of the solution approach:Step 1: Generating the Suffix ArrayA suffix array can be generated using various algorithms such as Manber-Myers algorithm or Kasai's algorithm. In this example, we will use the Manber-Myers algorithm. The steps involved are as follows:a) Create an array of structures to store the suffixes and their corresponding indices.b) Sort the suffixes lexicographically.c) Update the indices of the sorted suffixes.For our example string S = "abcaabca", the resulting suffix array will be [7, 4, 0, 5, 1, 6, 2, 3].Step 2: Finding the Longest Common PrefixWe iterate through the sorted suffixes and compare adjacent pairs to find the longest common prefix. To do this, we keep track of the length of the common prefix using the LCP (Longest Common Prefix) array.The LCP array is an array of integers where LCP[i] represents the length of the longest common prefix between the suffixes at indices i and (i-1).In our example, the LCP array will be [0, 1, 3, 0, 2, 2, 1, 0].Step 3: Calculating AnswerTo obtain the length of the longest common prefix among all the suffixes, except for the suffix at index 0, we need to find the maximum value in the LCP array. We traverse the LCP array and track the maximum value encountered. In our example, the maximum value is 3.Hence, the length of the longest common prefix among all the suffixes of S, except for the suffix starting at index 0, is 3.Conclusion:In this article, we explored an example problem from the AtCoder Beginner Contest that required the usage of suffix arrays. By generating the suffix array and utilizing the LCP array, we were able to efficiently find the length of the longest common prefix among all the suffixes, except for the suffix at index 0. Suffix arrays are powerful data structures that find applications in various string manipulation and analysis problems.。

Java后缀数组-求sa数组

Java后缀数组-求sa数组

Java后缀数组-求sa数组后缀数组的⼀些基本概念请⾃⾏百度,简单来说后缀数组就是⼀个字符串所有后缀⼤⼩排序后的⼀个集合,然后我们根据后缀数组的⼀些性质就可以实现各种需求。

1public class MySuffixArrayTest {23public char[] suffix;//原始字符串45public int n;//字符串长度67public int[] rank;// Suffix[i]在所有后缀中的排名89public int[] sa;// 满⾜Suffix[SA[1]] < Suffix[SA[2]] …… < Suffix[SA[Len]],即排名为i的后缀为Suffix[SA[i]]10// (与Rank是互逆运算)1112public int[] height;// 表⽰Suffix[SA[i]]和Suffix[SA[i - 1]]的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀1314public int[] h;// 等于Height[Rank[i]],也就是后缀Suffix[i]和它前⼀名的后缀的最长公共前缀1516public int[] ws;// 计数排序辅助数组1718public int[] y;// 第⼆关键字rank数组1920public int[] x;// rank的辅助数组21}以下的讲解都以"aabaaaab"这个字符串为例,先展⽰⼀下结果,请参考这个结果进⾏理解分析(这个结果图我复制别⼈的,请各位默认下标减1,因为我的数组从下标0开始的)suffix:原始字符串数组假设原始字符串是"aabaaaab" 那这个数组对应的值应该是{'a','a','b','a','a','a','a','b'}n:字符串长度 这⾥n是8rank: 后缀数组的名次数组相当于存的是第i个后缀对应的名次是多少⽐如rank[0]就是指"aabaaaab"这个后缀的名次 rank[1]指"abaaaab"这个后缀的名次sa: 这个是和rank数组互逆的⼀个数组存的是第x名的是哪个后缀还是举例⼦来说明 sa[0]指的是排名第⼀的后缀数组即为3 也就是"aaaab"这个数组他对应的rank[3]就是0。

详细解析后缀数组(RMQ及LCP)

详细解析后缀数组(RMQ及LCP)
询问数组height中下标从 i+1 到 j 范围内所有元素的最小值
经典的RMQ (Range Minimum Query)问题!!!
➢线段树、排序树 —— O(nlogn)预处理 , O(logn)每次询问 ➢标准RMQ方法 —— O(n)预处理 , O(1)每次询问
后缀数组——辅助工具
采用一种“神奇的”方法,可以在O(n)时间内计算出height数组 采用标准RMQ方法在O(n)时间内进行预处理 之后就可以在常数时间内算出任何的LCP(i,j)
名次数组
后缀数组——构造方法
如何构造后缀数组?
把n个后缀当作n个字符串,按照普通的方法进行排序 —— O(n2)
低效的原因 —— 把后缀仅仅当作普通的、独立 的字符串,忽略了后缀之间存在的有机联系。
后缀数组——构造方法
u[1..k] ,len(u)≥k 对字符串u,定义uk =
u
,len(u)<k
LCP Theorem 对任何1≤i<j≤n LCP(i,j)=min{LCP(k-1,k) | i+1≤k≤j} 称j-i为LCP(i,j)的“跨度”,LCP Theorem意义为: 跨度大于1的LCP值可以表示成一段跨度等于1的LCP值的最小值
后缀数组——辅助工具
LCP(i,j)=3
4=LCP(i,i+1)
后缀是一种特殊的子串 从某个位置i开始到整个串的末尾结束 S的从i开头的后缀等价于S[i..len(S)]
后缀数组——定义和符号
约定一个字符集Σ 待处理的字符串约定为S,约定len(S)=n 规定S以字符“$”结尾,即S[n]=“$”
“$”小于Σ中所有的字符 除了S[n]=“$”之外,S的其他字符都属于Σ
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

后缀数组、不重复子串Distinct Substrings题目大意:给出一个字符串,问它的不重复子串有多少个。

两题是一样的,除了字符串长度..分析:用后缀数组可以轻松解决。

因为这个字符串的每个子串必然是某个后缀的前缀,先用后缀数组求出sa和height,那么对于sa[k],它有n-sa[k]个子串,其中有height[k]个是和上一个后缀重复的,所以要减去。

所以用后缀数组求解的时间复杂度是O(n),后缀数组要是用倍增算法是O(nlog2n),效率很高。

note:wa了一次,主要原因是忘了a[n]=0这个关键的初值...PS:各位大牛对我的差劲的c++代码有什么看法可以尽管喷哈!codes:#include<iostream>#include<cstring>using namespace std;const long maxn=1010;long wn[maxn],wa[maxn],wb[maxn],wv[maxn],a[maxn],sa[maxn],rank[maxn],height[maxn]; char r[maxn];long cmp(long *r,long a,long b,long l){return r[a]==r[b]&&r[a+l]==r[b+l];}void da(long *r,long *sa,long n,long m){long i,j,p,*x=wa,*y=wb,*t;for (i=0;i<m;i++) wn[i]=0;for (i=0;i<n;i++) wn[x[i]=r[i]]++;for (i=1;i<m;i++) wn[i]+=wn[i-1];for (i=n-1;i>=0;i--) sa[--wn[x[i]]]=i;for (p=1,j=1;p<n;j*=2,m=p){for (p=0,i=n-j;i<n;i++) y[p++]=i;for (i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;for (i=0;i<m;i++) wn[i]=0;for (i=0;i<n;i++) wn[wv[i]=x[y[i]]]++;for (i=1;i<m;i++) wn[i]+=wn[i-1];for (i=n-1;i>=0;i--) sa[--wn[wv[i]]]=y[i];for (t=x,x=y,y=t,x[sa[0]]=0,p=1,i=1;i<n;i++)x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;}return;}void calheight(long *r,long *sa,long n){long i,j,k=0;for (i=1;i<=n;i++) {rank[sa[i]]=i;height[i]=0;}for (i=0;i<n;height[rank[i++]]=k)for (k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);return;}int main(){long t,i;cin >> t;while (t--){cin >> r;long n=strlen(r);for (int i=0;i<n;i++) a[i]=static_cast<int>(r[i]);a[n]=0;da(a,sa,n+1,256);calheight(a,sa,n);long sum=0;for (i=1;i<=n;i++) sum+=n-sa[i]-height[i];cout << sum << endl;}return 0;}----------------------------------------------------------------------------------------------------------------------------[后缀数组]最长重复子串分析:任何一个重复子串,必然是某两个后缀的公共前缀。

由于任何两个后缀的最长公共前缀是一段height中的最小值,所以最长的一个重复子串应该是height中的最大值。

note:1、sa的代码还需要继续写啊- -总是有几个地方容易错..特别注意那个for(i=n-1;i>=0;i--)以及cmp(y,sa[i-1],sa[i],j)。

2、w数组理解错误,开小了- -实际应该开maxn。

结果re了一次..#include<iostream>#include<cstring>using namespace std;const int maxn=100010;int w[maxn],wa[maxn],wb[maxn],wv[maxn],a[maxn],sa[maxn],rank[maxn],height[maxn];int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}void da(int *r,int *sa,int n,int m){int i,j,p,*x=wa,*y=wb,*t;for (i=0;i<m;i++) w[i]=0;for (i=0;i<n;i++) w[x[i]=r[i]]++;for (i=1;i<m;i++) w[i]+=w[i-1];for (i=n-1;i>=0;i--) sa[--w[x[i]]]=i;for (p=1,j=1;p<n;j*=2,m=p){for (p=0,i=n-j;i<n;i++) y[p++]=i;for (i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;for (i=0;i<m;i++) w[i]=0;for (i=0;i<n;i++) w[wv[i]=x[y[i]]]++;for (i=1;i<m;i++) w[i]+=w[i-1];for (i=n-1;i>=0;i--) sa[--w[wv[i]]]=y[i];for (t=x,x=y,y=t,p=1,i=1,x[sa[0]]=0;i<n;i++)x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;}return;}void cal(int *r,int *sa,int n){int i,j,k=0;for (i=1;i<=n;i++) rank[sa[i]]=i;for (i=0;i<n;height[rank[i++]]=k)for (k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);return;}int main(){int t,i,j;cin >> t;while (t--){char s[maxn];cin >> s;int n=strlen(s);for (i=0;i<n;i++) a[i]=static_cast<int>(s[i]);a[n]=0;da(a,sa,n+1,255);cal(a,sa,n);int max=0,k=0;for (i=1;i<=n;i++)if (max<height[i]){max=height[i];k=sa[i];}for (i=k,j=0;j<max;j++,i++) cout << s[i];cout << endl;}return 0;}----------------------------------------------------------------------------------------------------------------------------[后缀数组、最长重复不重叠子串]题意:给出一个旋律,用n个数字[1,88]表示其音符,问它最长的主题长度是多少。

一个旋律的主题是一段至少出现过两次的不重叠音乐片段。

所谓重复出现,包括一段音乐全体加上某个数后再次出现。

如1 2 3 4 5和5 6 7 8 9是同一个音乐片段。

主题长度至少为5.无解输出0。

分析:若把整个旋律看成一个字符串,这个问题有点像求最长重复不重叠子串。

稍作思考不难发现,两段音乐片段相同时,这两段音乐片段相邻两数字的差一样。

如1 2 3 4 5可以看做1 1 1 1,5 6 7 8 9也可以看做1 1 1 1,所以这两段音乐相同。

看出这点,就不难解决本题了。

先将输入转化成两两相邻的数的差,再把整个数组看做一个字符串,则问题转化为求最长重复不重叠子串的长度,而答案是这个长度+1。

另外,第一个数字与上一个数的差不存在,我们可以将它设为一个很大的值,使得它对最终结果不影响。

要解决上述问题,可以使用后缀数组。

先二分答案,将问题转化为判定性问题,然后利用height数组来对后缀进行分组,贪心判断即可。

具体的可以参考罗穗骞的论文,我这里主要讲些细节。

1、使用后缀数组的话,数组中的元素应该>0,而直接做差会使数组中的元素变成负数,所以可以在做差后加上一个足够大的常数,来避免负数的出现。

2、二分答案要写好很不容易。

我现在还有点糊涂- -3、输入用scanf。

题目里也有这个提示...codes:#include<stdio.h>using namespace std;const int maxn=20010;intw[maxn],wa[maxn],wb[maxn],wv[maxn],a[maxn],x[maxn],sa[maxn],rank[maxn],height[maxn],n; int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}void da(int *r,int *sa,int n,int m){int i,j,p,*x=wa,*y=wb,*t;for (i=0;i<m;i++) w[i]=0;for (i=0;i<n;i++) w[x[i]=r[i]]++;for (i=1;i<m;i++) w[i]+=w[i-1];for (i=n-1;i>=0;i--) sa[--w[x[i]]]=i;for (p=1,j=1;p<n;j*=2,m=p){for (p=0,i=n-j;i<n;i++) y[p++]=i;for (i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;for (i=0;i<m;i++) w[i]=0;for (i=0;i<n;i++) w[wv[i]=x[y[i]]]++;for (i=1;i<m;i++) w[i]+=w[i-1];for (i=n-1;i>=0;i--) sa[--w[wv[i]]]=y[i];for (t=x,x=y,y=t,p=1,i=1,x[sa[0]]=0;i<n;i++)x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;}return;}void cal(int *r,int *sa,int n){int i,j,k=0;for (i=1;i<=n;i++) rank[sa[i]]=i;for (i=0;i<n;height[rank[i++]]=k)for (k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);return;}int check(int k){int max=0,min=maxn;for (int i=1;i<=n;i++){if (height[i]<k){max=min=sa[i];}else{max=max>sa[i]?max:sa[i];min=min<sa[i]?min:sa[i];if (max-min>=k) return 1;}}if (max-min>=k) return 1; else return 0;}int Bin_search(int l,int r){int mid;for (mid=(l+r)>>1;l<=r;mid=(l+r)>>1){if (check(mid)) l=mid+1; else r=mid-1;}if (check(mid)) return mid; else return 0;}int main(){scanf("%d",&n);while (n){int i,k;for (i=0;i<n;i++) scanf("%d",&x[i]);a[0]=500;for (i=1;i<n;i++) a[i]=x[i]-x[i-1]+100;a[n]=0;da(a,sa,n+1,501);cal(a,sa,n);k=Bin_search(0,n/2)+1;if (k<5) k=0;printf("%d\n",k);scanf("%d",&n);}return 0;}----------------------------------------------------------------------------------------------------------------------------[后缀数组、出现k次的重复子串]题目大意:给出n个数字组成的一个字符串,求最长的恰好出现k次的重复子串(可重叠)的字符串的长度。

相关文档
最新文档