文章目录
后缀数组中的数组意思
① Rank[i] 表示第 i 个后缀的排名是 Rank[i]
② sa[i] 表示排名为 i 的后缀原来是第 sa[i] 个
③ height[i] 表示排名为 i 和 i−1 的两个后缀的 lcp
lcp(i,j) 为排名第 i 到排名第 j 的后缀的最长公共前缀
后缀数组的一些性质
下标都是从1开始滴
①:排名第 i 的后缀的长度为: n−sa[i]+1
因为假如现在是第 k 个后缀,那么第 k 个后缀的长度是不是就是 n−k+1
那现在排名第 i 的后缀就是原来第 sa[i]个后缀,那么长度就是 n−sa[i]+1
我找的这个模板下标什么的都是从1开始的,处理[1,n],所以每次会有个a[n+1]=0,T组的时候一直wa,改了这个就对了T_T
感觉好多题目怎么用后缀数组求我都转换不过来,现在来总结一哈
hihocoder 1403 : 后缀数组一·重复旋律
题目链接:http://hihocoder.com/problemset/problem/1403?sid=1372120
重复k次对应的就是连续k-1个height,而这连续的height 的最小值就是他们重复k次的子串的最长值
#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int maxn=1e5+5;
const int MOD=1e9+7;
int SA[maxn],Rank[maxn];
int C1[maxn],C2[maxn],A[maxn],B[maxn],tsa[maxn];//基数排序的数组,以及辅助数组
int Height[maxn];//最重要的Height数组,Height[i]表示排名第i的后缀和排名第i-1的后缀的lcp
int a[maxn];//把字符串转成数字存下来
void da(int n,int m)//n是字符串长度,m是字符串的范围
{
for(int i=0; i<=m; i++)C1[i]=0;
for(int i=1; i<=n; i++)C1[a[i]]++;
for(int i=2; i<=m; i++)C1[i]+=C1[i-1];
for(int i=n; i>=1; i--)SA[C1[a[i]]--]=i;
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(a[SA[i]]!=a[SA[i-1]])Rank[SA[i]]++;
}
for(int len=1; Rank[SA[n]]<n; len<<=1)
{
for(int i=0; i<=n; i++)C1[i]=C2[i]=0;//这里一定是要弄到n
for(int i=1; i<=n; i++)
{
A[i]=Rank[i];
if(i+len<=n)B[i]=Rank[i+len];
else B[i]=0;
C1[A[i]]++;
C2[B[i]]++;
}
for(int i=1; i<=n; i++)C1[i]+=C1[i-1],C2[i]+=C2[i-1];
for(int i=n; i>=1; i--)tsa[C2[B[i]]--]=i;
for(int i=n; i>=1; i--)SA[C1[A[tsa[i]]]--]=tsa[i];
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(A[SA[i]]!=A[SA[i-1]]||B[SA[i]]!=B[SA[i-1]])Rank[SA[i]]++;
}
}
for(int i=1,h=0; i<=n; i++) //求Height数组
{
if(h)h--;
while(a[i+h]==a[SA[Rank[i]-1]+h])h++;
Height[Rank[i]]=h;
}
}
int que[maxn];
int N,K;
int que_min(int k)//单调队列求连续k个元素的最小值
{
int Max=0;//来求每段区间的最大值
if(k==1)
{
for(int i=1;i<=N;i++)Max=max(Max,Height[i]);
return Max;
}
int top=1,tail=0;
for(int i=1;i<=N;i++)
{
while(i-que[top]>=k)top++;
if(tail<top||Height[i]>=Height[que[tail]])que[++tail]=i;
else
{
while(tail>=top&&Height[i]<Height[que[tail]])if(top>--tail)break;
que[++tail]=i;
}
if(i>=k)Max=max(Max,Height[que[top]]);
}
return Max;
}
int main()
{
while(cin>>N>>K)
{
for(int i=1;i<=N;i++)scanf("%d",a+i);
da(N,100);
cout<<que_min(K-1)<<endl;
}
}
hihocoder 1407 : 后缀数组二·重复旋律2
题目链接:http://hihocoder.com/problemset/problem/1407?sid=1372226
题目要求是:求出现次数至少为 k=2 次的最长的不可重叠的串是多长?
这里因为是2次重复也就是连续 k-1 个height,也就是找最连续一个最大的height嘛
但是还要看找出的这两个height对应的串会不会重叠,所以用sa来找他们原来的原来的位置。
因为只用看这种情况有没有,所以直接找极值看行不行
#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int maxn=1e6+5;
const int MOD=1e9+7;
int SA[maxn],Rank[maxn];
int C1[maxn],C2[maxn],A[maxn],B[maxn],tsa[maxn];//基数排序的数组,以及辅助数组
int Height[maxn];//最重要的Height数组,Height[i]表示排名第i的后缀和排名第i-1的后缀的lcp
int a[maxn];//把字符串转成数字存下来
void da(int n,int m)//n是字符串长度,m是字符串的范围
{
for(int i=0; i<=m; i++)C1[i]=0;
for(int i=1; i<=n; i++)C1[a[i]]++;
for(int i=2; i<=m; i++)C1[i]+=C1[i-1];
for(int i=n; i>=1; i--)SA[C1[a[i]]--]=i;
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(a[SA[i]]!=a[SA[i-1]])Rank[SA[i]]++;
}
for(int len=1; Rank[SA[n]]<n; len<<=1)
{
for(int i=0; i<=n; i++)C1[i]=C2[i]=0;//这里一定是要弄到n
for(int i=1; i<=n; i++)
{
A[i]=Rank[i];
if(i+len<=n)B[i]=Rank[i+len];
else B[i]=0;
C1[A[i]]++;
C2[B[i]]++;
}
for(int i=1; i<=n; i++)C1[i]+=C1[i-1],C2[i]+=C2[i-1];
for(int i=n; i>=1; i--)tsa[C2[B[i]]--]=i;
for(int i=n; i>=1; i--)SA[C1[A[tsa[i]]]--]=tsa[i];
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(A[SA[i]]!=A[SA[i-1]]||B[SA[i]]!=B[SA[i-1]])Rank[SA[i]]++;
}
}
for(int i=1,h=0; i<=n; i++) //求Height数组
{
if(h)h--;
while(a[i+h]==a[SA[Rank[i]-1]+h])h++;
Height[Rank[i]]=h;
}
}
int N;
int check(int len)
{
int mx=0,mi=N+1;
for(int i=1;i<=N;i++)
{
if(Height[i]<len)continue;
//因为一个height是看的排第i和i-1的,所以在这两个里面找出最大位置和最小位置
mx=max(mx,SA[i]);
mi=min(mi,SA[i]);
if(i-1>=1)mx=max(mx,SA[i-1]);
if(i-1>=1)mi=min(mi,SA[i-1]);
if(mx-mi>=len)return 1;
}
return 0;
}
int main()
{
while(cin>>N)
{
for(int i=1;i<=N;i++)scanf("%d",a+i);
da(N,1005);//a[i]的范围是1000,不是普通字符串的255了
int L=1,R=N,ans;
while(L<=R)
{
int mid=L+R>>1;
if(check(mid))
{
ans=mid;
L=mid+1;
}
else R=mid-1;
}
cout<<ans<<endl;
}
}
hihocoder 1415 : 后缀数组三·重复旋律3
题目链接:http://hihocoder.com/problemset/problem/1415
题意:求两个串的最长公共子串
他是把两个串拼接到一起,然后看找height的最大值,并且height对应的两个sa[i]和sa[i-1]要在不同的两个串中就行了
同理,如果是要求k个串的最长公共子串的话,那就是找连续k-1 个height的最大值,并且还要满足每个sa都是在不同的串中
#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int maxn=1e6+5;
const int MOD=1e9+7;
int SA[maxn],Rank[maxn];
int C1[maxn],C2[maxn],A[maxn],B[maxn],tsa[maxn];//基数排序的数组,以及辅助数组
int Height[maxn];//最重要的Height数组,Height[i]表示排名第i的后缀和排名第i-1的后缀的lcp
int a[maxn];//把字符串转成数字存下来
void da(int n,int m)//n是字符串长度,m是字符串的范围
{
for(int i=0; i<=m; i++)C1[i]=0;
for(int i=1; i<=n; i++)C1[a[i]]++;
for(int i=2; i<=m; i++)C1[i]+=C1[i-1];
for(int i=n; i>=1; i--)SA[C1[a[i]]--]=i;
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(a[SA[i]]!=a[SA[i-1]])Rank[SA[i]]++;
}
for(int len=1; Rank[SA[n]]<n; len<<=1)
{
for(int i=0; i<=n; i++)C1[i]=C2[i]=0;//这里一定是要弄到n
for(int i=1; i<=n; i++)
{
A[i]=Rank[i];
if(i+len<=n)B[i]=Rank[i+len];
else B[i]=0;
C1[A[i]]++;
C2[B[i]]++;
}
for(int i=1; i<=n; i++)C1[i]+=C1[i-1],C2[i]+=C2[i-1];
for(int i=n; i>=1; i--)tsa[C2[B[i]]--]=i;
for(int i=n; i>=1; i--)SA[C1[A[tsa[i]]]--]=tsa[i];
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(A[SA[i]]!=A[SA[i-1]]||B[SA[i]]!=B[SA[i-1]])Rank[SA[i]]++;
}
}
for(int i=1,h=0; i<=n; i++) //求Height数组
{
if(h)h--;
while(a[i+h]==a[SA[Rank[i]-1]+h])h++;
Height[Rank[i]]=h;
}
}
char s[maxn];
int main()
{
cin>>(s+1); //第一个字符串
int len=strlen(s+1);
cin>>(s+len+1); //第二个字符串
int N=strlen(s+1);
for(int i=1;i<=N;i++)a[i]=s[i]-'a'+1;
da(N,300);
int Max=0;
for(int i=2;i<=N;i++)
{
int p1=SA[i-1],p2=SA[i];
if((p1<=len&&p2>len)||(p2<=len&&p1>len))//两段能岔开就行,不管谁在前面谁在后面
Max=max(Max,Height[i]);
}
cout<<Max<<endl;
}
hdu 6194 string string string
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6194
题意:求恰好出现k次的子串有多少种?
这道题跟以前做的有点不一样,不是求长度啥的,而是求出现k次的子串有多少种
比如样例:
3
abcabcabcabc
出现三次的子串有
abca
abcab
abcabc
bca
bacb
bacbc
ca
cab
cabc
以上9种都是
根据女学长的思路,我们好求的是大于等于k次的子串有多少种,然后减去大于等于k+1次的就行了,但是这个大于等于k次的要怎么求呀?
我们这样想,要找等于k次的,不就是找连续k-1个height么?假如找出的这连续k-1个height的最长公共前缀为lcp,那么至少都有lcp种
比如找出来的lcp为:abca
那么abca是这k-1个的最长公共前缀
abc也是
ab也是
a也是
也就是说他的4个前缀都阔以,所以最少都有lcp个
而第一个a和最后一个a又是重复的,所以说为什么这样求出来是大于等于k次的
然后就是容斥这里我感觉我好喃理解哦
枚举长度为k的串,假如枚举到[i,j]
那么包含长度为k的串长度为k+1的阔以是[i-1,j],也阔以是[i,j+1],
#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int maxn=1e6+5;
const int MOD=1e9+7;
int SA[maxn],Rank[maxn];
int C1[maxn],C2[maxn],A[maxn],B[maxn],tsa[maxn];//基数排序的数组,以及辅助数组
int Height[maxn];//最重要的Height数组,Height[i]表示排名第i的后缀和排名第i-1的后缀的lcp
int a[maxn];//把字符串转成数字存下来
void da(int n,int m)//n是字符串长度,m是字符串的范围
{
for(int i=0; i<=m; i++)C1[i]=0;
for(int i=1; i<=n; i++)C1[a[i]]++;
for(int i=2; i<=m; i++)C1[i]+=C1[i-1];
for(int i=n; i>=1; i--)SA[C1[a[i]]--]=i;
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(a[SA[i]]!=a[SA[i-1]])Rank[SA[i]]++;
}
for(int len=1; Rank[SA[n]]<n; len<<=1)
{
for(int i=0; i<=n; i++)C1[i]=C2[i]=0;//这里一定是要弄到n
for(int i=1; i<=n; i++)
{
A[i]=Rank[i];
if(i+len<=n)B[i]=Rank[i+len];
else B[i]=0;
C1[A[i]]++;
C2[B[i]]++;
}
for(int i=1; i<=n; i++)C1[i]+=C1[i-1],C2[i]+=C2[i-1];
for(int i=n; i>=1; i--)tsa[C2[B[i]]--]=i;
for(int i=n; i>=1; i--)SA[C1[A[tsa[i]]]--]=tsa[i];
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(A[SA[i]]!=A[SA[i-1]]||B[SA[i]]!=B[SA[i-1]])Rank[SA[i]]++;
}
}
for(int i=1,h=0; i<=n; i++) //求Height数组
{
if(h)h--;
while(a[i+h]==a[SA[Rank[i]-1]+h])h++;
Height[Rank[i]]=h;
}
}
int N;
char s[maxn];
int dp[maxn][30];
int lcp(int L,int R)
{
if(L==R)return N-SA[L]+1;
L++;//加1是因为height[i]包含了i-1的值了
int k=0;
while((1<<(k+1))<R-L+1)k++;
return min(dp[L][k],dp[R-(1<<k)+1][k]);
}
int main()
{
int T,K;
cin>>T;
while(T--)
{
cin>>K;
cin>>(s+1);
N=strlen(s+1);
for(int i=1; i<=N; i++)a[i]=s[i]-'a'+1;
a[N+1]=0;//后一位要清零,因为这个wa了好多遍
da(N,300);
for(int i=1; i<=N; i++)dp[i][0]=Height[i];
for(int j=1; (1<<j)<=N; j++)
{
for(int i=1; i+(1<<j)-1<=N; i++)
{
dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
LL ans=0;
for(int i=1,j; i<=N; i++)
{
j=i+K-1;
if(j<=N)ans+=lcp(i,j);
if(i-1>=1&&j<=N)ans-=lcp(i-1,j);
if(j+1<=N)ans-=lcp(i,j+1);
if(i-1>=1&&j+1<=N)ans+=lcp(i-1,j+1);
}
cout<<ans<<endl;
}
}
2018焦作网络赛 H-String and Times
题目链接:https://nanti.jisuanke.com/t/31717
我的后缀数组的模板竟然有问题。。。
倍增那个循环
for(int len=1; len<n; len<<=1)
必选要改成这样才不会超时
for(int len=1; Rank[SA[n]]<n; len<<=1)
我也不知道为什么,感觉就我那样写也没有问题呀,没有陷入死循环的机会呀T_T
然后这道题就跟上面hdu 6194 差不多了,只不过这次求的刚好多少次重复是个范围了
#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int maxn=1e5+5;
const int MOD=1e9+7;
int SA[maxn],Rank[maxn];
int C1[maxn],C2[maxn],A[maxn],B[maxn],tsa[maxn];//基数排序的数组,以及辅助数组
int Height[maxn];//最重要的Height数组,Height[i]表示排名第i的后缀和排名第i-1的后缀的lcp
int a[maxn];//把字符串转成数字存下来
void da(int n,int m)//n是字符串长度,m是字符串的范围
{
for(int i=0; i<=m; i++)C1[i]=0;
for(int i=1; i<=n; i++)C1[a[i]]++;
for(int i=2; i<=m; i++)C1[i]+=C1[i-1];
for(int i=n; i>=1; i--)SA[C1[a[i]]--]=i;
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(a[SA[i]]!=a[SA[i-1]])Rank[SA[i]]++;
}
for(int len=1; Rank[SA[n]]<n; len<<=1)
{
for(int i=0; i<=n; i++)C1[i]=C2[i]=0;//这里一定是要弄到n
for(int i=1; i<=n; i++)
{
A[i]=Rank[i];
if(i+len<=n)B[i]=Rank[i+len];
else B[i]=0;
C1[A[i]]++;
C2[B[i]]++;
}
for(int i=1; i<=n; i++)C1[i]+=C1[i-1],C2[i]+=C2[i-1];
for(int i=n; i>=1; i--)tsa[C2[B[i]]--]=i;
for(int i=n; i>=1; i--)SA[C1[A[tsa[i]]]--]=tsa[i];
Rank[SA[1]]=1;
for(int i=2; i<=n; i++)
{
Rank[SA[i]]=Rank[SA[i-1]];
if(A[SA[i]]!=A[SA[i-1]]||B[SA[i]]!=B[SA[i-1]])Rank[SA[i]]++;
}
}
for(int i=1,h=0; i<=n; i++) //求Height数组
{
if(h)h--;
while(a[i+h]==a[SA[Rank[i]-1]+h])h++;
Height[Rank[i]]=h;
}
}
int N;
char s[maxn];
int dp[maxn][30];
int lcp(int L,int R)
{
if(L==R)return N-SA[L]+1;
L++;//加1是因为height[i]包含了i-1的值了
int k=0;
while((1<<(k+1))<R-L+1)k++;
return min(dp[L][k],dp[R-(1<<k)+1][k]);
}
int main()
{
int cnt1,cnt2;
while(scanf("%s%d%d",s+1,&cnt1,&cnt2)!=EOF)
{
N=strlen(s+1);
for(int i=1; i<=N; i++)a[i]=s[i]-'A'+1;
a[N+1]=0;
da(N,27);
for(int i=1; i<=N; i++)dp[i][0]=Height[i];
for(int j=1; (1<<j)<=N+1; j++)
{
for(int i=1; i+(1<<j)-1<=N+1; i++)
{
dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
LL ans=0;
for(int i=1,j,k; i+cnt1-1<=N; i++)
{
j=i+cnt1-1;
k=i+cnt2-1;
if(j<=N)ans+=lcp(i,j);
if(i-1>=1&&j<=N)ans-=lcp(i-1,j);
if(k+1<=N)ans-=lcp(i,k+1);
if(i-1>=1&&k+1<=N)ans+=lcp(i-1,k+1);
}
printf("%lld\n",ans);
}
}