string stringstring


Time Limit: 2000/1000 MS(Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2240    Accepted Submission(s): 703


Problem Description

Uncle Mao is awonderful ACMER. One day he met an easy problem, but Uncle Mao was so lazy thathe left the problem to you. I hope you can give him a solution.
Given a string s, we define a substring that happens exactly k times as animportant string, and you need to find out how many substrings which areimportant strings.

Input

The first linecontains an integer T (T≤100) implying thenumber of test cases.
For each test case, there are two lines:
the first line contains an integer k (k≥1) which is describedabove;
the second line contain a string s (length(s)≤105).
It's guaranteed that ∑length(s)≤2∗106. 

Output

For each testcase, print the number of the important substrings in a line.

Sample Input

2

2

abcabc

3

abcabcabcabc

Sample Output

6

9

Source

2017 ACM/ICPC Asia Regional Shenyang Online





        据说这题是另一道SPOJ的一道后缀自动机的弱化版,好腻害的样子……这里用后缀数组解。


        大致题意,给你一个字符串,然后问你在这个字符串中,恰好出现k次的不同子串有多少个(可以相互重叠)。


        首先明确所有后缀数组题目都很重要的一点,即所有子串都是某个后缀的前缀,大部分都可以转化为lcp的问题。然后我之前在POJ上做了一道题,没有写博客,是求一个字符串中最长的出现至少k次的子串的长度(​​POJ 3261​​)。这两道题目类似,在求至少k次的时候用的是同样的方法。


        对于这个出现至少k次的问题,我们可以这么考虑。如果一个子串出现了多次,设出现的起始位置为i和j,那么他们的后缀排名sa[]一定是相邻的,即abs(sa[i]-s[j])=1。如此一来,如果出现k次,那么这k个子串对应的后缀排名也是连续k个,且重复出现的部分最大长度即为这连续一段的height[]的rmq。所以说,至少出现k次,相当于在长度为k的height数组的区间内求rmq,也即区间的很多后缀的lcp是这个区间的最小值。


        对于POJ那题,我们二分一个最大长度,然后扫描height数组,看最大连续height值大于k的长度是否大于这个二分的长度即可。而对于此题,设长度为k的区间的最长公共前缀为lcp,那么就会产生lcp个新的满足条件的子串,统计即可。但是,注意了,这个并不是我们要求的。我们需要的是出现恰好k次(exactly),而不是至少k次。然而这个问题也是经典问题,很容易解决。我们用容斥原理,用至少k次的答案减去至少k+1次的答案即可。而这个k+1多出来的1可以是往前多出现一次或者往后多出现一次,所以说两次都要减去。不过在减的过程中,我们发现多减了一个出现k+2的答案,这个画一个区间图更好理解。按照这样子容斥累加最后答案即可。


        最后要注意,当k==1的时候,会发现rmq区间是一个单位区间,这个时候的lcp不能是对应height数组的值,显然应该就是这个后缀本身的长度,这里做一个特判。然后还需要用LL。具体见代码:

#include<bits/stdc++.h>
#define LL long long
#define N 100010

using namespace std;

struct Suffix_Array
{
int sa[N],Rank[N],h[N],n;
int xx[N],yy[N],c[N]; char *s;
bool cmp(int *s,int x,int y,int k)
{return (s[x]==s[y])&&(s[x+k]==s[y+k]);}
void ins(char *str) {s=str;n=strlen(s)+1;}

void DA()
{
memset(c,0,sizeof(c));
int *x=xx,*y=yy,m=130,*t,i;
for(i=0;i<n;i++) x[i]=s[i];
for(i=0;i<n;i++) c[x[i]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for(int k=1,tot=0;tot<n;k<<=1,m=tot)
{
memset(c,0,sizeof(c));
for(i=0;i<n;i++) c[x[i]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-k,tot=0;i<n;i++) y[tot++]=i;
for(i=0;i<n;i++) if (sa[i]>=k) y[tot++]=sa[i]-k;
for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
for(i=tot=1,t=x,x=y,y=t,x[sa[0]]=0;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?tot-1:tot++;
}
}

void cal_height()
{
int i,j,k=0;
for(i=1;i<n;i++) Rank[sa[i]]=i;
for(i=0;i<n-1;h[Rank[i++]]=k)
for(k?k--:0,j=sa[Rank[i]-1];s[i+k]==s[j+k];k++);
}

} SA;

struct ST
{
struct node{int min,l,r;} tree[N<<2];

inline void build(int i,int l,int r)
{
int mid=(l+r)>>1;
tree[i].r=r; tree[i].l=l;
if (l==r){tree[i].min=SA.h[l];return;}
build(i<<1,l,mid);build(i<<1|1,mid+1,r);
tree[i].min=min(tree[i<<1].min,tree[i<<1|1].min);
}

inline int getmin(int i,int l,int r)
{
if (l>r) return 0;
if (tree[i].l==l&&tree[i].r==r) return tree[i].min;
int mid=(tree[i].l+tree[i].r)>>1;
if (mid>=r) return getmin(i<<1,l,r);
else if (mid<l) return getmin(i<<1|1,l,r);
else return min(getmin(i<<1,l,mid),getmin(i<<1|1,mid+1,r));
}

} seg;

char s[N];
int n,k;

int lcp(int l,int r)
{
if (l==r) return n-SA.sa[l];
return seg.getmin(1,l+1,r);
}

int main()
{
int T_T;
cin>>T_T;
while(T_T--)
{
LL ans=0;
scanf("%d",&k);
scanf("%s",s);SA.ins(s);
SA.DA();SA.cal_height();
seg.build(1,1,n=strlen(s));
for(int i=1;i+k-1<=n;i++)
{
ans+=lcp(i,i+k-1);
if (i>1) ans-=lcp(i-1,i+k-1);
if (i+k<=n) ans-=lcp(i,i+k);
if (i>1&&i+k<=n) ans+=lcp(i-1,i+k);
}
printf("%I64d\n",ans);
}
return 0;
}