【字符串】后缀数组

 参考文章:

数据结构 —— 字符串:后缀数组_Jetiaime的博客-CSDN博客(算法代码)

后缀数组_KonjakLAF的博客-CSDN博客(应用+例题)

板子:

#include
using namespace std;
typedef long long ll;
const int N=1e7+5;
const int inf=1<<30;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,k;
char s[N];
int m,sa[N],cnt[N],rk[N],id[N],px[N],oldrk[N];

inline bool cmp(int x,int y,int k){
	return oldrk[x]==oldrk[y]&&oldrk[x+k]==oldrk[y+k];
}
inline void getsa(){
	//初始化 
	m=10;
	for(int i=1;i<=n;i++)cnt[rk[i]=s[i]-'0']++;
	for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
	//for(int i=1;i<=9;i++)printf("cnt[%d]:%d\n",i,cnt[i]);
	for(int i=n;i;i--)sa[cnt[rk[i]]--]=i;
	//for(int i=1;i<=n;i++)printf("sa[%d]:%d %s\n",i,sa[i],s+sa[i]); 
	//倍增
	for(int k=1;k<=n;k<<=1){//后缀长度 
		int idx=0;
		for(int i=n;n-ik)id[++idx]=sa[i]-k;//按后一半排序的后缀
		memset(cnt,0,sizeof(cnt));
		for(int i=1;i<=n;i++)cnt[px[i]=rk[id[i]]]++;
		for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
		for(int i=n;i;i--)sa[cnt[px[i]]--]=id[i];
		idx=0;swap(oldrk,rk);
		for(int i=1;i<=n;i++)rk[sa[i]]=cmp(sa[i],sa[i-1],k)?idx:++idx;
		m=idx;
        if(n==m)break;
		//for(int i=1;i<=n;i++)printf("sa[%d]:%d %s\n",i,sa[i],s+sa[i]); 
	}//for(int i=1;i<=n;i++)printf("sa[%d]:%d %s\n",i,sa[i],s+sa[i]); 
}
int val[N],len;
inline void add(ll x){
	ll pre=x;
	for(int i=1;i<=len;i++){
		if(!pre)break;
		val[i]+=pre;
		pre=val[i]/10;
		val[i]%=10;
	}while(pre)val[++len]=pre%10,pre/=10;
}
int main(){
	scanf("%d%d",&n,&k);
	scanf("%s",s+1);
	getsa();
	ll res=0;len=n-k;
	for(int i=n;i;i--){
		if(sa[i]<=k+1){//sa[i]--sa[i]+n-k
			//printf("sa[%d]:%d %s\n",i,sa[i],s+sa[i]); 
			for(int j=sa[i],anj=len;j<=sa[i]+n-k-1;j++,anj--){
				//printf("s[%d]:%c %d\n",j,s[j],s[j]-'0');
				//res=res*10+int(s[j]-'0');
				val[anj]=s[j]-'0';
			}
			for(int j=1;j

例题: 

例1:P3809 【模板】后缀排序 - 洛谷 (luogu.com.cn) 

#include
using namespace std;
typedef long long ll;
const int N=1e6+5;
const int inf=1<<30;
const ll INF=0x3f3f3f3f3f3f3f3f;
char s[N];
int l1,l2,n;
int idx,m,sa[N],rk[N],oldrk[N],px[N],id[N],c[N];
bool cmp(int x,int y,int k){
	return oldrk[x]==oldrk[y]&&oldrk[x+k]==oldrk[y+k];
}
void getsa(){
	//初始化
	m=150;
	for(int i=1;i<=n;i++)c[rk[i]=s[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	//for(int i=1;i<=m;i++)printf("c[%d]%d\n",i,c[i]);
	for(int i=n;i;i--)sa[c[rk[i]]--]=i;
	//for(int i=1;i<=n;i++)printf("sa[%d]%d %s\n",i,sa[i],s+sa[i]);
	//倍增
	for(int k=1;k<=n;k<<=1){
		idx=0;
		for(int i=n;n-ik)id[++idx]=sa[i]-k;
		memset(c,0,sizeof(c));
		for(int i=1;i<=n;i++)c[px[i]=rk[id[i]]]++;
		for(int i=1;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i;i--)sa[c[px[i]]--]=id[i];
		swap(rk,oldrk);
		idx=0;
		for(int i=1;i<=n;i++)rk[sa[i]]=cmp(sa[i],sa[i-1],k)?idx:++idx;
        if(idx==n)break;
		m=idx;
		//for(int i=1;i<=n;i++)printf("sa[%d]%d %s\n",i,sa[i],s+sa[i]);
	} 
}
int ht[N];
void geth(){
	for(int i=1,k=0;i<=n;i++){
		if(k)k--;
		while(s[i+k]==s[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
}

inline void read(int &x){
	int f=1;
	x=0;
	char ch=getchar();
	while(ch<'0'||'9'9)write(x/10);
	putchar('0'+x%10);
}
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);

	getsa();geth();
	for(int i=1;i<=n;i++)printf("%d ",sa[i]);
	
	return 0;
}

例2:数字串 -matiji 

题解:找到最大的n-k位数

eg. 6 2 121312

样例:
6 2
121312
排序后:
12
121312
1312
2
21312
312

倒序遍历找到最大的n-k位数

注意:*1.由于字符串长度最大1e6,会爆ll,所以存储每一位的数字然后输出

           *2.getsa()函数中没有 if(n==m)break; 会超时

#include
using namespace std;
typedef long long ll;
const int N=1e7+5;
const int inf=1<<30;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,k;
char s[N];
int m,sa[N],cnt[N],rk[N],id[N],px[N],oldrk[N];

inline bool cmp(int x,int y,int k){
	return oldrk[x]==oldrk[y]&&oldrk[x+k]==oldrk[y+k];
}
inline void getsa(){
	//初始化 
	m=10;
	for(int i=1;i<=n;i++)cnt[rk[i]=s[i]-'0']++;
	for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
	//for(int i=1;i<=9;i++)printf("cnt[%d]:%d\n",i,cnt[i]);
	for(int i=n;i;i--)sa[cnt[rk[i]]--]=i;
	//for(int i=1;i<=n;i++)printf("sa[%d]:%d %s\n",i,sa[i],s+sa[i]); 
	//倍增
	for(int k=1;k<=n;k<<=1){//后缀长度 
		int idx=0;
		for(int i=n;n-ik)id[++idx]=sa[i]-k;//按后一半排序的后缀
		memset(cnt,0,sizeof(cnt));
		for(int i=1;i<=n;i++)cnt[px[i]=rk[id[i]]]++;
		for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
		for(int i=n;i;i--)sa[cnt[px[i]]--]=id[i];
		idx=0;swap(oldrk,rk);
		for(int i=1;i<=n;i++)rk[sa[i]]=cmp(sa[i],sa[i-1],k)?idx:++idx;
		m=idx;
        if(n==m)break;
		//for(int i=1;i<=n;i++)printf("sa[%d]:%d %s\n",i,sa[i],s+sa[i]); 
	}//for(int i=1;i<=n;i++)printf("sa[%d]:%d %s\n",i,sa[i],s+sa[i]); 
}
int val[N],len;
inline void add(ll x){
	ll pre=x;
	for(int i=1;i<=len;i++){
		if(!pre)break;
		val[i]+=pre;
		pre=val[i]/10;
		val[i]%=10;
	}while(pre)val[++len]=pre%10,pre/=10;
}
int main(){
	scanf("%d%d",&n,&k);
	scanf("%s",s+1);
	getsa();
	ll res=0;len=n-k;
	for(int i=n;i;i--){
		if(sa[i]<=k+1){//sa[i]--sa[i]+n-k
			//printf("sa[%d]:%d %s\n",i,sa[i],s+sa[i]); 
			for(int j=sa[i],anj=len;j<=sa[i]+n-k-1;j++,anj--){
				//printf("s[%d]:%c %d\n",j,s[j],s[j]-'0');
				//res=res*10+int(s[j]-'0');
				val[anj]=s[j]-'0';
			}
			for(int j=1;j

例3:乐曲主题 - 洛谷 (最长不重叠公共子串)

转调后仍相同则差值相同,此时只需找不重叠的最长公共子串

可转化为不同后缀的最长公共前缀,且不同后缀的公共前缀不重叠 

后缀数组计算时cnt[a[i]]计数,而差值a[i]可能为负数如 1-88,在此给每一个a[i]+90 

有子串长度为 4 时 "主题" 长度为 5 

二分答案,答案变成判定性问题。

将 height [ i ]分为连续的组,如果有连续的一段 height≥mid ,且 max(sai)−min(sai)>mid

说明存在长度为 mid且不重叠的重复子串。

*对 height [ i ] 分组的方法很重要。

#include
using namespace std;
typedef long long ll;
const int N=5e3+5;
const int inf=1<<30;
const ll INF=0x3f3f3f3f3f3f3f3f;

inline void read(ll &x){
	ll f=1;
	x=0;
	char ch=getchar();
	while(ch<'0'||'9'9)write(x/10);
	putchar('0'+x%10);
}
int n,a[N];
int m,sa[N],rk[N],oldrk[N],px[N],id[N],idx,ht[N],c[N];
bool cmp(int x,int y,int k){
	return oldrk[x]==oldrk[y]&&oldrk[x+k]==oldrk[y+k];
}
void getsa(){
	//初始化 
	m=180;
	for(int i=1;i<=n;i++)c[rk[i]=a[i]]++;
	for(int i=2;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i;i--)sa[c[rk[i]]--]=i;
	//倍增
	for(int k=1;k<=n;k<<=1){
		idx=0;
		for(int i=n;n-ik)id[++idx]=sa[i]-k;
		memset(c,0,sizeof(c));
		for(int i=1;i<=n;i++)c[px[i]=rk[id[i]]]++;
		for(int i=2;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i;i--)sa[c[px[i]]--]=id[i];
		//更新rk 
		swap(rk,oldrk);idx=0;
		for(int i=1;i<=n;i++)rk[sa[i]]=cmp(sa[i],sa[i-1],k)?idx:++idx;
		if(idx==n)break;
		m=idx;
	} 
}
void geth(){
	for(int i=1,k=0;i<=n;i++){
		if(k)k--;
		while(a[i+k]==a[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
}
int chk(int x){
	int mn=sa[1],mx=sa[1];
	for(int i=2;i<=n;i++){
		if(ht[i]x)return 1;
		}
	}return 0;
}
/*
a 0
aabcdd 1
aabckm 4
aabcko 5
bac 0
此时ht为1 4 5的是一组 sa[i]max-sa[i]min+1 >= mid则不重复    
*/
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i 转化为判定问题 */
	//for(int i=1;i<=n;i++)printf("a[%d]%d\n",i,a[i]);printf("\n");
	
	//for(int i=1;i=4?res+1:0);
	return 0;
}


 

你可能感兴趣的:(字符串,算法)