后缀数组中的数组意思

R a n k [ i ] Rank[i] Rank[i] 表示第 i i i 个后缀的排名是 R a n k [ i ] Rank[i] Rank[i]
s a [ i ] sa[i] sa[i] 表示排名为 i i i 的后缀原来是第 s a [ i ] sa[i] sa[i]
h e i g h t [ i ] height[i] height[i] 表示排名为 i i i i 1 i-1 i1 的两个后缀的 l c p lcp lcp
l c p ( i , j ) lcp(i,j) lcp(i,j) 为排名第 i i i 到排名第 j j j 的后缀的最长公共前缀

后缀数组的一些性质

下标都是从1开始滴
①:排名第 i i i 的后缀的长度为: n s a [ i ] + 1 n-sa[i]+1 nsa[i]+1
因为假如现在是第 k k k 个后缀,那么第 k k k 个后缀的长度是不是就是 n k + 1 n-k+1 nk+1
那现在排名第 i i i 的后缀就是原来第 s a [ i ] sa[i] sa[i]个后缀,那么长度就是 n s a [ i ] + 1 n-sa[i]+1 nsa[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 k=2 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);
	}
}