字符串里算难了。。参考了好多博客
推荐https://blog.csdn.net/YxuanwKeith/article/details/50636898
每个数组的含义:sa[i]:排在第i位的子串的位置,、
rank[i]:位置 i 的子串排在第几位
height[i]:位置为sa[i]的后缀和位置为sa[i-1]的后缀的最长公共前缀,即排名相邻的两个后缀的最长公共前缀
height[rank[i]]:相邻两个后缀的最长公共前缀
tax[i]:桶
tp[i]:基数排序辅助数组,意义类似于sa[i],即按第二关键字顺序排在第i位的子串的位置
rsort()函数:基数排序:用更新sa数组
void rsort(){//桶排序,每个桶是第一关键字,桶内次序是第二关键字 for(int i=0;i<=m;i++)tax[i]=0;//桶清空 for(int i=1;i<=n;i++)tax[rank[tp[i]]]++;//把子串按照第二关键字次序放进桶中,越先放进桶内的第二关键字越小 for(int i=1;i<=m;i++)tax[i]+=tax[i-1];//求桶的前缀和 for(int i=n;i>=1;i--)sa[tax[rank[tp[i]]]--]=tp[i];//按第二关键字倒序从桶中抽出子串,tp[i]即是对应子串的位置 }
suffix()函数:求出sa数组,rank数组,并最终求出height数组
void suffix(){ for(int i=1;i<=n;i++) rank[i]=a[i],tp[i]=i; int p; m=127; rsort(); for(int w=1;p<n;w<<=1,m=p){//桶的个数更新 for(int i=n-w+1,p=0;i<=n;i++) tp[++p]=i;//长度越界的串第二关键字是0(即排在最前面) for(int i=1;i<=n;i++) if(sa[i]>w) tp[++p]=sa[i]-w;//未越界则用上一层的sa求出tp数组 rsort();swap(rank,tp);rank[sa[1]]=1;p=1;//更新sa数组,将旧rank转给tp for(int i=2;i<=n;i++) rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;//离散化rank(把相等的字符串的rank设置为相同),子串相等的用同一个序号 } //求出height数组 int j,k=0; for(int i=1;i<=n;height[rank[i++]]=k) for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k) ; }
如何求出height数组
利用性质height[rank[i]] >= height[rank[i-1]]-1
证明:设suffix[k]是suffix[i-1]前一名的后缀,则其其最长公共前缀是height[rank[i-1]],都去掉第一个字符,就变成suffix[k+1]和suffix[i],
显然suffix[k+1]排在suffix[i]前面,并且其最长公共前缀为height[rank[i-1]]-1(就是去掉了第一个字符,其他是一样的)
那么height[rank[i]] 要么 就是 suffix[k+1], 要么比suffix[k+1]排名更靠近suffix[i],所以height[rank[i]] >= height[rank[i-1]]-1
不用直接求height,而是通过height[rank[i]]间接求
//求出height数组
int j,k=0; for(int i=1;i<=n;height[rank[i++]]=k) for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k)//k=height[rank[i-1]]-1,j是证明中的k, ;
cmp()函数:比较两个二元串是否完全相等
int cmp(int *f,int x,int y,int w){return f[x]==f[y] && f[x+w]==f[y+w];}
下面是例题:求字符串中出现两次及以上的子串
/* 后缀数组:suffix[i]:字符串第i-len的子串,即i开始的后缀 sa[i]:按字典序排在第i个的是第sa[i]个后缀 rank[i]:第i个后缀按字典序排在rank【i]位 height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,即排在第i个后缀和第i-1个后缀的公共前缀 性质1:rank[j]<rank[k]==>后缀j...k的最长公共前缀是min(height[rank[j]+1],height[rank[j]+2]...height[rank[k]]) 假设后缀j的排名小于k,那么这些排名连续的后缀的最长前缀就是上面公式 性质2:height[rank[i]]>=height[rank[i-1]]-1 证明:设suffix[k]是排在suffix[i-1]前一名的后缀,则按照height数组定义,其最长公共前缀是height[rank[i-1]] 显然suffix[k+1]排在suffix[i]前面,并且其最长公共前缀为height[rank[i-1]]-1 所以height[rank[i]]即suffix[i]和其前一名的最长公共前缀,必定不小于height[rank[i-1]]-1 可以按照height[rank[1]],height[rank[2]]...计算顺序 所有后缀子串的前缀子串即为原串的所有子串 怎么构造rank和sa数组?基数排序即可 怎么构造height数组?利用性质2即可 */ #include<iostream> #include<cstring> #include<cstdio> #include<algorihtm> using namespace std; #define maxn 100005 char ch[maxn],all[maxn]; int sa[maxn],rank[maxn],height[maxn],tax[maxn],tp[maxn],a[maxn],n,m; //rank[i]:第i个后缀的排名,sa[i]排名i的后缀的位置; //height[i]排名为i的后缀与排名为i-1的后缀的最长公共前缀 //tax[i]基数排序辅助数组(就是桶),tp[i],rank的辅助数组(第二关键字),类似于sa数组 //为什么要增加一个tp数组,因为前几次排序后可能有好多个串的排序号是一样的,这时sa[i]就无法精确表示第i个串是第几个串 //通过增加tp数组,tp[i]表示排名i的串是第几个串,rank[tp[i]]表示i在双关键字排序下的序号(rank[tp[i]]==rank[tp[j]]的情况) //a 原串 char str[maxn]; void rsort(){//基数排序 for(int i=0;i<=m;i++) tax[i]=0;//每个桶清空 for(int i=1;i<=n;i++) tax[rank[tp[i]]]++;//桶上累计 for(int i=1;i<=m;i++) tax[i]+=tax[i-1];//桶累加求前缀和,以此求出所有每个桶对应的序号 for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];//找到那个桶的值,对应这个排名的就是第tp[i]个数,之后把桶里的数减1即可! } int cmp(int *f,int x,int y,int w){return f[x]==f[y] && f[x+w]==f[y+w];}//比较两个关键字 void suffix(){ for(int i=1;i<=n;i++) rank[i]=a[i],tp[i]=i; m=127,rsort();//一开始以单个字符为单位,所以m=127 for(int w=1,p=1;p<n;w<<=1,m=p){//倍增法,不断更新rank //w:当前子串长度,m:当前离散后排名种类数 //tp:第二关键字,可以由上一次sa得到 p=0; for(int i=n-w+1;i<=n;i++) tp[++p]=i;//长度越界,第二关键字为0,退化为下标即可 for(int i=1;i<=n;i++) if(sa[i]>w) tp[++p]=sa[i]-w;//排名为p的第二关键字是属于串中下标为a[i]-w的; //字符串首w位不会变成第二关键字 //要使rank[sa[i]]=i,更新sa,用tp暂存上一轮的rank,离散rank rsort(),swap(rank,tp),rank[sa[1]]=p=1; //用已经完成的sa来更新rank,并离散化:把相等字符串的rank设置为相同 for(int i=2;i<=n;i++) rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p,++p; } //求出height数组 int j,k=0; for(int i=1;i<=n;height[rank[i++]]=k) for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k)//k=height[rank[i-1]]-1,j是证明中的k, ; } void init(){ scanf("%s",str); n=strlen(str); for(int i=0;i<n;i++) a[i+1]=str[i]; } int main(){ init(); suffix(); int ans=height[2]; for(int i=3;i<=n;i++) ans+=max(height[i]-height[i-1],0); printf("%d\n",ans); } //询问一个字符串中有多少至少出现两次的子串, //height[i]是两个后缀的前缀长度,为答案做的贡献是以前缀首字母开始的长度从1到height[i]个子串 //同时该贡献要减去height[i-1]的贡献,即如果首字母相同的情况下造成的重复计算 //但是如果这个差小于0,那么显然第i个后缀和第i-1个后缀首字母就不相同,那么这种情况贡献是0
关于height数组的一些应用:height[i]是排名i和排名i-1的两个后缀的最长公共前缀
因为height数组是两个排名相邻的串的前缀,那么全串的最长公共前缀必是max{height[i]}
1.求一个串中两个串的最大公共前缀:
就是height数组,用rmq预处理,再O(1)查询,
2.一个串中可重叠的重复最长子串:即求任意两个后缀的最长公共前缀,而任意两个后缀的最长公共前缀都是height