Common Substrings
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 11519 Accepted: 3814
Description
A substring of a string T is defined as:
T(i, k)=TiTi+1…Ti+k-1, 1≤i≤i+k-1≤|T|.
Given two strings A, B and one integer K, we define S, a set of triples (i, j, k):
S = {(i, j, k) | k≥K, A(i, k)=B(j, k)}.
You are to give the value of |S| for specific A, B and K.
Input
The input file contains several blocks of data. For each block, the first line contains one integer K, followed by two lines containing strings A and B, respectively. The input file is ended by K=0.
1 ≤ |A|, |B| ≤ 105
1 ≤ K ≤ min{|A|, |B|}
Characters of A and B are all Latin letters.
Output
For each case, output an integer |S|.
Sample Input
2
aababaa
abaabaa
1
xx
xx
0
Sample Output
22
5
问两个串大于等于k的长度的公共子串个数
后缀数组拼起来,
一个很明显的想法是一个块区间的首和尾的最大lcp是之间的最小值,所以对于每个属于B串的子串,我们要找出前面所有的A串和它的公共子串且大于等于k的个数,这样每个串的贡献应该是每个A串和B串和这个串公共子串,lcp-k+1,如何求每个A串和当前B串的lcp呢,暴力的话是on^2,不可行,由于开始我们把大于k的都放在一个块里面,所以在一个块里面,必然每个A都会对下面的B有贡献的,很明显我们是可以o1算出每个A串的贡献的 sum+=height数组-k+1,但是这是算到最大的贡献,很有可能当你遇到这个B的时候这两个公共lcp 比height数组小很多,所以使用单调栈,因为两者的lcp只会越来越小,把这些A串的height存在栈里面,如果这个height比当前的大,那么证明这个贡献必须减少,所以
while(tail>0&&st[tail]>height[i])
{
sum-=(1ll*st[tail]-k+1)*cnt[tail];
sum+=(1ll*height[i]-k+1)*cnt[tail];
num+=cnt[tail];
tail--;
}
我们同时用cnt数组记录这个串承载了多少了个串的本事,因为他们的贡献将变一样,所以就可以完全弹走了,而算A串的时候,B串没有初始化为num=1,所以必然不会有B串的贡献,很很很巧妙
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 2e5+500;
int t1[MAXN],t2[MAXN],c[MAXN];
int len1,len2;
bool cmp(int *r,int a,int b,int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(char str[],int sa[],int ra[],int height[],int n,int m)
{
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[i]=str[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(j=1;j<=n;j<<=1)
{
p=0;
for(i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[y[i]]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n) break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++) ra[sa[i]]=i;
for(i=0;i<n;i++)
{
if(k) k--;
j=sa[ra[i]-1];
while(str[i+k]==str[j+k])k++;
height[ra[i]]=k;
}
}
int ra[MAXN],height[MAXN];
int sa[MAXN],num[MAXN];
char str[MAXN];
int st[MAXN];
typedef long long ll;
ll cnt[MAXN];
ll solve(int k,int n)
{
int tail=0;
ll sum=0,ans=0;
for(int i=1;i<=n;i++)
{
if(height[i]<k)
{
sum=0;
tail=0;
continue;
}
ll num=0;
while(tail>0&&st[tail]>height[i])
{
sum-=(1ll*st[tail]-k+1)*cnt[tail];
sum+=(1ll*height[i]-k+1)*cnt[tail];
num+=cnt[tail];
tail--;
}
st[++tail]=height[i];
if(sa[i-1]<len1)
{
sum+=(1ll*height[i]-k+1);
cnt[tail]=num+1;
}
else cnt[tail]=num;
if(sa[i]>len1) ans+=sum;
}
tail=0;
sum=0;
for(int i=1;i<=n;i++)
{
if(height[i]<k)
{
sum=0;
tail=0;
continue;
}
ll num=0;
while(tail>0&&st[tail]>height[i])
{
sum-=(1ll*st[tail]-k+1)*cnt[tail];
sum+=(1ll*height[i]-k+1)*cnt[tail];
num+=cnt[tail];
tail--;
}
st[++tail]=height[i];
if(sa[i-1]>len1)
{
sum+=(1ll*height[i]-k+1);
cnt[tail]=num+1;
}
else cnt[tail]=num;
if(sa[i]<len1)
ans+=sum;
}
return ans;
}
int main()
{
int k;
while(~scanf("%d",&k))
{
if(!k) break;
scanf("%s",str);
len1=strlen(str);
str[len1]='#';
scanf("%s",str+len1+1);
len2=strlen(str);
da(str,sa,ra,height,len2,130);
printf("%lld\n",solve(k,len2) );
}
}
Max 数组 能接受的字符串的最长长度
Min 数组 能接受的字符串的最小长度 和Max[fail[p]] 一样
right 能接受的字符串在原串中出现位置的集合
sz right的大小
Max-Min+1 就是每个状态的子串的数目
后缀自动机的做法就是,利用寻找最长公共子串这个匹配,遇到这个点,如果最大匹配长度大于等于k,那么
更新的是 ans+=Max[p]-max(Max[fail[i]]+1,k)+1)就好,然后更新父节点,首先匹配最长公共子串的时候能到这个点证明长度必然 >=Min[p] 那么父节点必然也可以更新,比较k和Min[p] 如果k大于等于Min[p],那么父节点必然没贡献,不然就是有贡献 标记一下,最后拓扑更新就行
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200000+5;
int last,tail,in[maxn],Min[maxn];
int Max[maxn],cnt[maxn],vis[maxn];
int nxt[maxn][26],fail[maxn];
char sa[maxn],sb[maxn];
typedef long long ll;
ll sz[maxn];
int Maxi[maxn];
inline void build(char *s)
{
while(*s)
{
int p=last,t=++tail,c=*s++-'a';
Max[t]=Max[p]+1;
sz[t]=1;
Maxi[t]=0;
while(p&&!nxt[p][c])
nxt[p][c]=t,p=fail[p];
if(p)
{
int q=nxt[p][c];
if(Max[q]==Max[p]+1)
fail[t]=q,Min[t]=Max[q]+1;
else
{
int k=++tail;
Maxi[k]=0;
fail[k]=fail[q];
fail[t]=fail[q]=k;
Max[k]=Max[p]+1;
memcpy(nxt[k],nxt[q],sizeof(nxt[q]));
while(p&&nxt[p][c]==q)
nxt[p][c]=k,p=fail[p];
}
}
else
fail[t]=Min[t]=1;
last=t;
}
}
int b[maxn];
ll flag[maxn];
int main()
{
int k;
while(~scanf("%d",&k))
{
if(!k) break;
int n;
memset(nxt,0,sizeof(nxt));
memset(b,0,sizeof(b));
memset(cnt,0,sizeof(cnt));
memset(flag,0,sizeof(flag));
scanf(" %s",sa);
last=1,tail=1;
build(sa);
int hh=strlen(sa);
for(int i=1;i<=tail;i++) cnt[Max[i]]++;
for(int i=1;i<=hh;i++) cnt[i]+=cnt[i-1];
for(int i=1;i<=tail;i++) b[cnt[Max[i]]--]=i;
for(int i=tail;i>=1;i--) sz[fail[b[i]]]+=sz[b[i]];
ll ans=0;
scanf(" %s",sb);
n=strlen(sb);
int p=1;
int len=0;
for(int i=0;i<n;i++)
{
int c=sb[i]-'a';
if(nxt[p][c]) len++,p=nxt[p][c];
else
{
while(p&&!nxt[p][c])
p=fail[p];
if(!p) p=1,len=0;
else {
len=Max[p]+1;
p=nxt[p][c];
}
}
// Maxi[p]=max(Maxi[p],len);
// if(len>=k) flag[p]++;
if(len>=k)
{
ans+=1ll*(len-max(k,Max[fail[p]]+1)+1)*sz[p];
if(k<=Max[fail[p]]) flag[fail[p]]++;
}
}
for(int i=tail;i>=1;i--)
{
int p=b[i];
ans+=max(0ll,1ll*flag[p]*(Max[p]-max(Max[fail[p]]+1,k)+1)*sz[p]);
if(k<=Max[fail[p]])
flag[fail[p]]+=flag[p];
}
printf("%lld\n",ans );
}
}