后缀数组&后缀自动机题目泛做

这几天想学后缀自动机的,结果发现后缀数组的坑还空着,索性填填吧。。

POJ2774
题目链接:http://poj.org/problem?id=2774

题解:
两个字符串之间加一个分隔符,求一发 s a [ i ] sa[i] sa[i]分别位于两个字符串中的 l c p lcp lcp即可,注意细节:数组两倍大、字符集大小等

代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#include <string>
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1 << 30;
const int maxn=200005;

char a[maxn],b[maxn],ori[maxn];
int ss[maxn],n;
int sa[maxn],bin[maxn],rk[maxn],tmp[maxn];

void getsa(char *s){
	n=strlen(s);int sz=max(n,27);
	for(int i=0;i<n;i++)rk[i]=s[i]-'a';
	
	for(int i=0;i<sz;i++)bin[i]=0;
	for(int i=0;i<n;i++)++bin[rk[i]];
	for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
	for(int i=n-1;i>=0;i--)sa[--bin[rk[i]]]=i;
	
	for(int j=1;j<=n;j<<=1){
		int p=0;
		for(int i=n-j;i<n;i++)tmp[p++]=i;
		for(int i=0;i<n;i++)
			if(sa[i]-j>=0)tmp[p++]=sa[i]-j;
		
		for(int i=0;i<sz;i++)bin[i]=0;
		for(int i=0;i<n;i++)++bin[rk[i]];
		for(int i=0;i<sz;i++)bin[i]+=bin[i-1];
		for(int i=n-1;i>=0;i--)sa[--bin[rk[tmp[i]]]]=tmp[i];
		
		p=0;
		tmp[sa[0]]=0;
		for(int i=1;i<n;i++){
			int v0=sa[i-1],v1=sa[i];int v00,v01;
			if(v0+j<n)v00=rk[v0+j];else v00=-1;
			if(v1+j<n)v01=rk[v1+j];else v01=-1;
			
			if(v00==v01&&rk[v0]==rk[v1])tmp[sa[i]]=p;
			else tmp[sa[i]]=++p;
		}
		
		for(int i=0;i<n;i++)rk[i]=tmp[i];
	}
}

int h[maxn];

int calc(int x,int y,int res,char *s){
	for(;x+res<n&&y+res<n&&s[x+res]==s[y+res];)++res;
	return res;
}

void geth(char *s){
	if(rk[0]==0)h[0]=0;else h[0]=calc(0,sa[rk[0]-1],0,s);
	
	for(int i=1;i<n;i++){
		if(rk[i]==0)h[i]=0;else h[i]=calc(i,sa[rk[i]-1],max(h[i-1]-1,0),s);
	}
}

int main(){
//	freopen("POJ2774.in","r",stdin);
//	freopen("POJ2774.out","w",stdout);
	scanf("%s",a);scanf("%s",b);
	sprintf(ori,"%s%c%s",a,'z'+1,b);
	n=strlen(ori);
	int la=strlen(a),lb=strlen(b);
	getsa(ori);
	geth(ori);
	
	int ans=0;
	for(int i=1;i<n;i++){
		if(h[sa[i]]>ans){
			if(0<=sa[i]&&sa[i]<la&&la<sa[i-1])ans=h[sa[i]];
			if(0<=sa[i-1]&&sa[i-1]<la&&la<sa[i])ans=h[sa[i]];
		}
	}
	printf("%d\n",ans);

	return 0;
}

SPOJ705 SPOJ694
题目链接:https://www.luogu.org/problemnew/show/SP705

题解:子串=后缀的前缀,发现恰好可以用后缀数组解决,考虑按照 s a sa sa的顺序加入,每加一个会产生 n − s a n-sa nsa个后缀(下标从0开始),但是会有 h [ s a [ i ] ] h[sa[i]] h[sa[i]]个重复的需要减掉

代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1 << 30;

const int maxn=1e5+5;

int bin[maxn],sa[maxn],rk[maxn],tmp[maxn];
char s[maxn];
int n,sz;

void getsa(){
	sz=max(n,128);
	
	for(int i=0;i<n;i++)rk[i]=s[i];
	for(int i=0;i<sz;i++)bin[i]=0;
	for(int i=0;i<n;i++)++bin[rk[i]];
	for(int i=1;i<sz;i++)bin[i]+=bin[i-1]; 
	for(int i=n-1;i>=0;i--)sa[--bin[rk[i]]]=i;
	
	for(int j=1;j<=n;j<<=1){
		int p=0;
		for(int i=n-j;i<n;i++)tmp[p++]=i;
		for(int i=0;i<n;i++)if(sa[i]-j>=0)tmp[p++]=sa[i]-j;
		
		for(int i=0;i<sz;i++)bin[i]=0;
		for(int i=0;i<n;i++)++bin[rk[i]];
		for(int i=1;i<sz;i++)bin[i]+=bin[i-1]; 
		for(int i=n-1;i>=0;i--)sa[--bin[rk[tmp[i]]]]=tmp[i];
		
		p=0;
		tmp[sa[0]]=0;
		for(int i=1;i<n;i++){
			int v0=sa[i-1],v1=sa[i],v00,v01;
			if(v0+j<n)v00=rk[v0+j];else v00=-1;
			if(v1+j<n)v01=rk[v1+j];else v01=-1;
			
			if(rk[sa[i-1]]==rk[sa[i]]&&v00==v01)tmp[sa[i]]=p;
			else tmp[sa[i]]=++p;
		}
		
		for(int i=0;i<n;i++)rk[i]=tmp[i];
	}
}

int h[maxn];

int calc(int x,int y,int res){
	for(;s[x+res]==s[y+res];)++res;
	return res;
}

void geth(){
	if(rk[0]==0)h[0]=0;
	else h[0]=calc(0,sa[rk[0]-1],0);
	
	for(int i=1;i<n;i++){
		if(rk[i]==0)h[i]=0;
		else h[i]=calc(i,sa[rk[i]-1],max(h[i-1]-1,0));
	}
}

void solve(){
	scanf("%s",s);
	n=strlen(s);
	
	getsa();
	geth();
	
	int ans=0;
	for(int i=0;i<n;i++){
		int curh=n-sa[i];
		ans+=curh-h[sa[i]];
	}
	printf("%d\n",ans);
}

int main(){
	int T;
	scanf("%d",&T);
	while(T--)solve();

	return 0;
}

BZOJ1717
题目链接:https://lydsy.com/JudgeOnline/problem.php?id=1717

题解:
题目首先可以转化为:计算出现了至少 k k k次的最长子串
有两种方法,首先都需要求出 s a sa sa h h h数组
1)在 h [ s a [ i ] ] h[sa[i]] h[sa[i]]上取连续 k − 1 k-1 k1个数的最小值,统计出来之后求最大值即可,可以用st表维护,正确性显然
2)二分,将 h [ s a [ i ] ] &lt; m i d h[sa[i]]&lt;mid h[sa[i]]<mid i i i断开,看是否有一段区间至少有连续的 k k k个元素
两者时间复杂度皆为 O ( n l o g n ) O(nlogn) O(nlogn)
两种方法实际表现经实测基本相同

1)的实现:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1 << 30;
const int maxn=1e6+5;

int n,k;
int a[100005];
int rk[100005],sa[100005],bin[maxn],tmp[100005];

void getsa(){
	int sz=1e6+1;
	for(int i=0;i<n;i++)rk[i]=a[i];
	
	for(int i=0;i<sz;i++)bin[i]=0;
	for(int i=0;i<n;i++)++bin[rk[i]];
	for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
	for(int i=n-1;i>=0;i--)sa[--bin[rk[i]]]=i;
	
	for(int j=1;j<=n;j<<=1){
		int p=0;
		for(int i=n-j;i<n;i++)tmp[p++]=i;
		for(int i=0;i<n;i++)if(sa[i]-j>=0)tmp[p++]=sa[i]-j;
		
		for(int i=0;i<sz;i++)bin[i]=0;
		for(int i=0;i<n;i++)++bin[rk[i]];
		for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
		for(int i=n-1;i>=0;i--)sa[--bin[rk[tmp[i]]]]=tmp[i];
		
		p=0;tmp[sa[0]]=0;
		for(int i=1;i<n;i++){
			int v0=sa[i-1],v1=sa[i],v00,v01;
			if(v0+j<n)v00=rk[v0+j];else v00=-1;
			if(v1+j<n)v01=rk[v1+j];else v01=-1;
			
			if(rk[sa[i-1]]==rk[sa[i]]&&v00==v01)tmp[sa[i]]=p;
			else tmp[sa[i]]=++p;
		}
		
		for(int i=0;i<n;i++)rk[i]=tmp[i];
	}
}

int h[maxn];

int calc(int x,int y,int res){
	for(;a[x+res]==a[y+res];)++res;
	return res;
}

void geth(){
	if(rk[0]==0)h[0]=0;else h[0]=calc(0,sa[rk[0]-1],0);
	for(int i=1;i<n;i++){
		if(rk[i]==0)h[i]=0;
		else h[i]=calc(i,sa[rk[i]-1],max(h[i-1]-1,0));
	}
}

int st[20005][24];
int ta[maxn];

void lsh(){
	memcpy(ta,a,sizeof a);
	sort(ta,ta+n);
	for(int i=0;i<n;i++)a[i]=lower_bound(ta,ta+n,a[i])-ta;
	for(int i=0;i<n;i++)++a[i];
}

int query(int x,int y){
	int k=(int)(1.0*log(y-x+1)/log(2.0));
	return min(st[x][k],st[y-(1<<k)+1][k]);
}

int main(){
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)scanf("%d",&a[i]);
	lsh();
	getsa();
	geth();
	
//	for(int i=0;i<n;i++)printf("%d ",sa[i]+1);puts("");
//	for(int i=0;i<n;i++)printf("%d ",h[sa[i]]);
//	puts("");
	for(int i=1;i<=n;i++)st[i][0]=h[sa[i-1]];
	for(int j=1;j<=20;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	
	int ans=0;
	for(int i=1;i+k-2<=n;i++)ans=max(ans,query(i,i+k-2));
	printf("%d\n",ans);

	return 0;
}

2)的代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1 << 30;
const int maxn=1e6+5;

int n,k;
int a[100005];
int rk[100005],sa[100005],bin[maxn],tmp[100005];

void getsa(){
	int sz=1e6+1;
	for(int i=0;i<n;i++)rk[i]=a[i];
	
	for(int i=0;i<sz;i++)bin[i]=0;
	for(int i=0;i<n;i++)++bin[rk[i]];
	for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
	for(int i=n-1;i>=0;i--)sa[--bin[rk[i]]]=i;
	
	for(int j=1;j<=n;j<<=1){
		int p=0;
		for(int i=n-j;i<n;i++)tmp[p++]=i;
		for(int i=0;i<n;i++)if(sa[i]-j>=0)tmp[p++]=sa[i]-j;
		
		for(int i=0;i<sz;i++)bin[i]=0;
		for(int i=0;i<n;i++)++bin[rk[i]];
		for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
		for(int i=n-1;i>=0;i--)sa[--bin[rk[tmp[i]]]]=tmp[i];
		
		p=0;tmp[sa[0]]=0;
		for(int i=1;i<n;i++){
			int v0=sa[i-1],v1=sa[i],v00,v01;
			if(v0+j<n)v00=rk[v0+j];else v00=-1;
			if(v1+j<n)v01=rk[v1+j];else v01=-1;
			
			if(rk[sa[i-1]]==rk[sa[i]]&&v00==v01)tmp[sa[i]]=p;
			else tmp[sa[i]]=++p;
		}
		
		for(int i=0;i<n;i++)rk[i]=tmp[i];
	}
}

int h[maxn];

int calc(int x,int y,int res){
	for(;a[x+res]==a[y+res];)++res;
	return res;
}

void geth(){
	if(rk[0]==0)h[0]=0;else h[0]=calc(0,sa[rk[0]-1],0);
	for(int i=1;i<n;i++){
		if(rk[i]==0)h[i]=0;
		else h[i]=calc(i,sa[rk[i]-1],max(h[i-1]-1,0));
	}
}

int st[20005][24];
int ta[maxn],height[maxn];

void lsh(){
	memcpy(ta,a,sizeof a);
	sort(ta,ta+n);
	for(int i=0;i<n;i++)a[i]=lower_bound(ta,ta+n,a[i])-ta;
	for(int i=0;i<n;i++)++a[i];
}

int check(int x){
	for(int i=2;i<=n;i++){
		int j;
		for(;height[i]<x&&i<=n;i++);
		for(j=i;height[j]>=x;j++);
		j--;	
		if((j-i+1)+1>=k)return 1;	// 额外的那个1是因为 height 是相邻的两个,所以j-i+1 相当于有j-i+1+1个数 
	}
	return 0;
}

int main(){
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)scanf("%d",&a[i]);
	lsh();
	getsa();
	geth();
	for(int i=1;i<=n;i++)height[i]=h[sa[i-1]];
	
	int l=1,r=n,ans;
	while(l<=r){
		int mid=l+r>>1;
//		printf("mid=%d, isok=%d\n",mid,check(mid));
		if(check(mid)){ans=mid;l=mid+1;}
		else r=mid-1;
	}
	printf("%d\n",ans);

	return 0;
}

BZOJ1031
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1031

题解:板子题,将字符串复制一遍,求一发 s a sa sa之后看 s a [ i ] sa[i] sa[i]对应的尾字符即可

代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1 << 30;
const int maxn=2e5+5;

char s[maxn],t[maxn];
int rk[maxn],sa[maxn],bin[maxn],tmp[maxn];
int n;

void getsa(){
	n=strlen(s);
	int sz=max(n,666);
	for(int i=0;i<n;i++)rk[i]=s[i];
	
	for(int i=0;i<sz;i++)bin[i]=0;
	for(int i=0;i<n;i++)++bin[rk[i]];
	for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
	for(int i=n-1;i>=0;i--)sa[--bin[rk[i]]]=i;
	
	for(int j=1;j<=n;j<<=1){
		int p=0;
		for(int i=n-j;i<n;i++)tmp[p++]=i;
		for(int i=0;i<n;i++)if(sa[i]-j>=0)tmp[p++]=sa[i]-j;
		
		for(int i=0;i<sz;i++)bin[i]=0;
		for(int i=0;i<n;i++)++bin[rk[i]];
		for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
		for(int i=n-1;i>=0;i--)sa[--bin[rk[tmp[i]]]]=tmp[i];
	
		p=0;tmp[sa[0]]=0;
		for(int i=1;i<n;i++){
			int v0=sa[i-1],v1=sa[i],v00,v01;
			if(v0+j<n)v00=rk[v0+j];else v00=-1;
			if(v1+j<n)v01=rk[v1+j];else v01=-1;
			
			if(rk[sa[i-1]]==rk[sa[i]]&&v00==v01)tmp[sa[i]]=p;
			else tmp[sa[i]]=++p;
		}
		
		for(int i=0;i<n;i++)rk[i]=tmp[i];
	}
}

int main(){
	scanf("%s",t);
	sprintf(s,"%s%s",t,t);
//	sprintf(s,"%s",t);
	getsa();
	int nt=strlen(t); 
	for(int i=0;i<n;i++){
		if(sa[i]<nt)
			printf("%c",s[sa[i]+nt-1]);	// sa[i] 是 字符串的开头,要得到结尾 
	}
	puts("");

	return 0;
}

POJ1226
题目链接:http://poj.org/problem?id=1226
题解:
首先将每个字符串正反拼起来,每个字符串之间用一个未出现过的字符连接
跑一遍后缀数组后考虑二分
二分 l e n len len为最长的长度,每次看连续的一段 h e i g h t height height是否存在 ≥ n \ge n n的长度 l c p lcp lcp都为 ≥ l e n \ge len len
如何判断两个字符串是同一字符串翻转得到的呢?
可以开一个 s e t set set记录当前得到的字符串都是属于哪个字符串翻转或不翻转得到的,这样在预处理的时候维护一个 i d x [ i ] idx[i] idx[i]表示拼起来之后的大字符串 s s s中的 s [ i ] s[i] s[i]是在第几个字符串中的就ok啦
有1个字符串的时候需要特判一下
PS:手抖把一个变量开到全局去了,因此RE 10次 QAQ

代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <set>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1 << 30;
const int maxn=5e4+5;

char tmps[maxn];
int s[maxn];
int rk[maxn],sa[maxn],bin[maxn],tmp[maxn],idx[maxn];
int n,num;

void getsa(){
	int sz=max(n,300);
	for(int i=0;i<n;i++)rk[i]=s[i]-'A';
	
	for(int i=0;i<sz;i++)bin[i]=0;
	for(int i=0;i<n;i++)++bin[rk[i]];
	for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
	for(int i=n-1;i>=0;i--)sa[--bin[rk[i]]]=i;
	
	for(int j=1;j<=n;j<<=1){
		int p=0;
		for(int i=n-j;i<n;i++)tmp[p++]=i;
		for(int i=0;i<n;i++)if(sa[i]-j>=0)tmp[p++]=sa[i]-j;
		
		for(int i=0;i<sz;i++)bin[i]=0;
		for(int i=0;i<n;i++)++bin[rk[i]];
		for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
		for(int i=n-1;i>=0;i--)sa[--bin[rk[tmp[i]]]]=tmp[i];
	
		p=0;tmp[sa[0]]=0;
		for(int i=1;i<n;i++){
			int v0=sa[i-1],v1=sa[i],v00,v01;
			if(v0+j<n)v00=rk[v0+j];else v00=-1;
			if(v1+j<n)v01=rk[v1+j];else v01=-1;
			
			if(rk[sa[i-1]]==rk[sa[i]]&&v00==v01)tmp[sa[i]]=p;
			else tmp[sa[i]]=++p;
		}
		
		for(int i=0;i<n;i++)rk[i]=tmp[i];
	}
}

int h[maxn];

int calc(int x,int y,int res){
	for(;s[x+res]==s[y+res];)++res;
	return res;
}

void geth(){
	if(rk[0]==0)h[0]=0;else h[0]=calc(0,sa[rk[0]-1],0);
	for(int i=1;i<n;i++){
		if(rk[i]==0)h[i]=0;
		else h[i]=calc(i,sa[rk[i]-1],max(h[i-1]-1,0));
	}
}

int check(int x){
	set<int>S;
	for(int i=1;i<n;i++){
		if(h[sa[i]]>=x){
			S.insert(idx[sa[i-1]]);S.insert(idx[sa[i]]); 
		}else{
			if(S.size()==num)return 1;
			S.clear();
		}
	}
	if(S.size()==num)return 1;
	return 0;
}

void solve(){
	n=0;memset(s,0,sizeof s);
	scanf("%d",&num);
	if(num==1){
		scanf("%s",tmps);
		printf("%d\n",strlen(tmps));
		return ;
	} 
	
	int delta=0;
	for(int i=1;i<=num;i++){
		scanf("%s",tmps);
		int tn=strlen(tmps);
		for(int j=0;j<tn;j++){
			idx[n]=i;
			s[n++]=tmps[j]+2*num+1;
		}
		idx[n]=i;
		s[n++]=delta+'A';++delta;
		
		for(int j=tn-1;j>=0;j--){
			idx[n]=i;
			s[n++]=tmps[j]+2*num+1; 
		}
		idx[n]=i;
		s[n++]=delta+'A';++delta; 
	}
	getsa();
	geth();
	
	int l=1,r=100,ans=0;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)){ans=mid;l=mid+1;}
		else r=mid-1;
	}
	printf("%d\n",ans);
}

int main(){
	int T;scanf("%d",&T);
	while(T--)solve();

	return 0;
}

BZOJ3238
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3238

题解:前两项找找规律就行了,是 ( n − 1 ) ∗ n ∗ ( n + 1 ) / 2 (n-1)*n*(n+1)/2 (n1)n(n+1)/2,主要是处理第3项
求出 h e i g h t height height之后答案就变成了 2 ∗ ∑ i , j m i n ( h e i g h t i + 1 . . . j ) 2*\sum_{i,j}min(height_{{i+1}...j}) 2i,jmin(heighti+1...j)这个可以用单调栈维护,其中单调栈 s t [ t o p ] st[top] st[top]是一个递增的栈,表示 h e i g h t [ i ] . . . h e i g h t [ n ] height[i]...height[n] height[i]...height[n]最小值是多少每次插入一个数的时候要弹掉不满足单调性的元素,这里面最小值也会发生改变:在这里插入图片描述
所以每次弹栈的时候需要减掉的是[A,B]这一段、[i,A]这一段(只是个比方,可能有很多段都需要减)这几段的最小值应是B而不是A,具体参考代码
还有一个就是代码中记录减的这个变量 t m p tmp tmp不应该每次都初始化,只应该初始化一次。
原因: t m p tmp tmp记录的是更改之后的值,如果每次都初始化的话,相当于上图的A抹掉了之后对后面无法产生影响,有 t m p tmp tmp记录的是A抹掉时变化的量

代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;
#define int LL

const int inf = 1 << 30;
const int maxn=1e6+5;

char s[maxn];
int n,sz;
int h[maxn],sa[maxn],rk[maxn],tmp[maxn],bin[maxn];
int height[maxn],st[maxn];

void getsa(){
	sz=max(n,27ll);
	for(int i=0;i<n;i++)rk[i]=s[i]-'a';

	for(int i=0;i<sz;i++)bin[i]=0;
	for(int i=0;i<n;i++)++bin[rk[i]];
	for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
	for(int i=n-1;i>=0;i--)sa[--bin[rk[i]]]=i;

	for(int j=1;j<=n;j<<=1){
		int p=0;
		for(int i=n-j;i<n;i++)tmp[p++]=i;
		for(int i=0;i<n;i++)if(sa[i]-j>=0)tmp[p++]=sa[i]-j;


		for(int i=0;i<sz;i++)bin[i]=0;
		for(int i=0;i<n;i++)++bin[rk[i]];
		for(int i=1;i<sz;i++)bin[i]+=bin[i-1];
		for(int i=n-1;i>=0;i--)sa[--bin[rk[tmp[i]]]]=tmp[i];

		p=0;tmp[sa[0]]=0;
		for(int i=1;i<n;i++){
			int v0=sa[i-1],v1=sa[i],v00,v01;
			if(v0+j<n)v00=rk[v0+j];else v00=-1;
			if(v1+j<n)v01=rk[v1+j];else v01=-1;

			if(rk[v0]==rk[v1]&&v00==v01)tmp[sa[i]]=p;
			else tmp[sa[i]]=++p;
		}

		for(int i=0;i<n;i++)rk[i]=tmp[i];
	}
}

int calc(int x,int y,int res){
	for(;s[x+res]==s[y+res];)++res;
	return res;
}

void geth(){
	if(rk[0]==0)h[0]=0;else h[0]=calc(0,sa[rk[0]-1],0);
	for(int i=1;i<n;i++){
		if(rk[i]==0)h[i]=0;
		else h[i]=calc(i,sa[rk[i]-1],max(h[i-1]-1,0ll));
	}
}

signed main(){
	scanf("%s",s);
	n=strlen(s);
	getsa();
	// for(int i=0;i<n;i++)printf("%d ",sa[i]+1);puts("");
	geth();
	// for(int i=1;i<n;i++)printf("%d ",h[sa[i]]);puts("");

	LL ans=(n-1)*n*(n+1)/2,top=0;
	for(int i=1;i<=n;i++)height[i]=h[sa[i-1]];
	height[0]=-inf;st[0]=0;
	LL tmp=0;	// tmp变量
	for(int i=1;i<=n;i++){
		while(height[st[top]]>height[i]){	// > 和 >= 均可以 
			tmp-=(st[top]-st[top-1])*height[st[top]];
			--top;
		}
		tmp+=(i-st[top])*height[i];st[++top]=i;
		ans-=2*tmp;
	}
	printf("%lld\n",ans);

	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值