Java教程

不同子串个数

本文主要是介绍不同子串个数,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

link

也算是一道模板题了。

上一道题并没有提到的是,后缀数组还有一个很重要的应用,即\(height\)数组,以下简称h。\(h_i\)的定义是排名为i的后缀与排名为i-1的后缀的最长公共前缀长度,而h数组我们可以\(O(N)\)求得。方法如下。

首先有一个结论,\(h[rank[i-1]]-1\le h[rank[i]]\),证明没有看懂,以后看懂了再来填坑。代码如下,先死记硬背吧。

for(int i=1,j;i<=m;h[a[i++]]=hh)
	for(hh?hh--:0,j=pl[a[i]-1];w[i+hh]==w[j+hh];hh++);

还留有一个问题,假如我们已经求出了h数组,那和本题要求的东西有什么关系吗。有,首先字串数量是很好求的,是\(len\times(len+1)/2\)。问题在于其中有多少个字串有重复。可以想到把这些重复子串按照左端点位置进行归类,对于同一类的重复子串,会发现存在一些后缀的某些前缀是相同的,这些一样的前缀即是重复子串,毕竟每一个后缀的前缀集合的并集即是原串的子串集合。而很明显这些前缀相同的后缀会在排序之后分到一起,它们的数量就是h数组所保存的。

代码。

#include<cstdio>
#include<cstring>
//#define zczc
#define ll long long
using namespace std;
const int N=100010;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
inline int get(){
	char w=getchar();
	while(w<'a'||w>'z')w=getchar();
	return w-'a'+2;
}
inline int max(int s1,int s2){
	return s1<s2?s2:s1;
}

int m,w[N],a[N];
struct node{
	int id,x,y;
}s[N],r[N];

int maxn,num[N],sum[N];
inline void init(){
	memset(num,0,sizeof(int)*(maxn+2));
	memset(sum,0,sizeof(int)*(maxn+2));
	maxn=0;return;
}
void solve(){
	for(int i=1;i<=m;i++)maxn=max(s[i].y,maxn),num[s[i].y]++;
	for(int i=1;i<=maxn;i++)sum[i]=sum[i-1]+num[i];
	for(int i=1;i<=m;i++)r[sum[s[i].y-1]+(num[s[i].y]--)]=s[i];init();
	for(int i=1;i<=m;i++)maxn=max(maxn,r[i].x),num[r[i].x]++;
	for(int i=1;i<=maxn;i++)sum[i]=sum[i-1]+num[i];
	for(int i=1;i<=m;i++)s[sum[r[i].x]-(--num[r[i].x])]=r[i];
}

int pl[N],h[N],hh;
void workh(){
	for(int i=1,j;i<=m;h[a[i++]]=hh)
		for(hh?hh--:0,j=pl[a[i]-1];w[i+hh]==w[j+hh];hh++);
	return;
}

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);for(int i=1;i<=m;i++)w[i]=a[i]=get();
	for(int n=1;n<=m;n<<=1){
		for(int i=1;i<=m;i++)s[i].id=i,s[i].x=a[i],s[i].y=i+n>m?1:a[i+n];solve();maxn=0;
		for(int i=1;i<=m;i++)a[s[i].id]=(i==1||s[i].x!=s[i-1].x||s[i].y!=s[i-1].y)?++maxn:maxn;
	}
	workh();ll ans=(ll)m*(m-1)>>1;
	for(int i=1;i<=m;i++)ans-=h[i];
	printf("%lld",ans+m);
	
	return 0;
}
这篇关于不同子串个数的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!