Common Substrings
Description A substring of a string T is defined as: Given two strings A, B and one integer K, we define S, a set of triples (i, 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 Output For each case, output an integer |S|. Sample Input 2 aababaa abaabaa 1 xx xx 0 Sample Output 22 5 Source
POJ Monthly--2007.10.06, wintokk
|
题意:
给你两个长度不超过10^5的字符串A,B。和一个数字K。K<=MIN(|A|,|B|)。现在问你A,B的公共子串中长度不小于K的有多少对。位置可以重叠。
思路:
既然涉及到公共子串容易想到的是利用后缀数组的heght数组。对于长度不小于K的公共子串。很容易到想把A,B串连接起来中间用一个特殊字符隔开按height数组分组。heght不小于k的连续height分一组。这样就可以保证同组内的公共前缀(子串)长度不小于K了。然后就是统计长度不小于K的公共子串的对数了。比较好想的做法是对组内每一个B的后缀。计算它与组内的每一个A的后缀能产生多少长度不小于K的公共子串。假设B额后缀b与A的后缀a的最长公共前缀的长度为len。那么他们能产生长度不小于K的公共子串的个数为len-K+1.为什么可以这样统计呢。因为不同的后缀在字符串的起点肯定不一样。所以统计子串只要统计后缀的前缀就行了。但是这样统计的话时间复杂度为O(n^2)。明显过不了。只能换一种思维统计。对于每一个B的后缀我们知道它与前面所有A的后缀能产生多少答案。然后对于A的每一个后缀它与前面B的后缀能产生多少答案。这样我们只要扫描两次就能得到最终答案了。现在关键就是怎样快速做到对于每一个B的后缀我们知道它与前面所有A的后缀能产生多少答案了。可以知道影响两个后缀的最长公共前缀的是中间最小的heght。所以我们想到用单挑栈来维护这个信息。这样我们就把heght一个一个往栈里加。这个栈的第i个元素维护目前有num[i]个A的后缀到当前位置的he[i]最小值是q[i]。这样我们每遇到一个A的后缀那么它对后面B的贡献为height[i]-K+1.一直到它到待计算B中间有height值小于它。所以我们记前面A对后面B的贡献为base。假设有新的height加进栈会使一num[a]个q[a],num[b]个q[b]。。。。的A的后缀到后面B的heght值变成height[i]。那么base=base-(q[a]-heght[i])*num[a]-(q[b]-heght[i])*num[b]。然后遇到B的后缀加上base就行了。统计A的前面B的后缀同理。这个问题就圆满解决了。
详细见代码:
#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=200020;
typedef long long ll;
int sa[maxn],T1[maxn],T2[maxn],he[maxn],rk[maxn],ct[maxn],q[maxn],num[maxn];
int n,m;
char txt[maxn];
ll ans;
void getsa(char *st)
{
int i,k,p,*x=T1,*y=T2;
for(i=0; i<m; i++) ct[i]=0;
for(i=0; i<n; i++) ct[x[i]=st[i]]++;
for(i=1; i<m; i++) ct[i]+=ct[i-1];
for(i=n-1; i>=0; i--)
sa[--ct[x[i]]]=i;
for(k=1,p=1; p<n; k<<=1,m=p)
{
for(p=0,i=n-k; i<n; i++) y[p++]=i;
for(i=0; i<n; i++) if(sa[i]>=k) y[p++]=sa[i]-k;
for(i=0; i<m; i++) ct[i]=0;
for(i=0; i<n; i++) ct[x[y[i]]]++;
for(i=1; i<m; i++) ct[i]+=ct[i-1];
for(i=n-1; i>=0; i--) sa[--ct[x[y[i]]]]=y[i];
for(swap(x,y),p=1,x[sa[0]]=0,i=1; i<n; i++)
x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
}
}
void gethe(char *st)
{
int i,j,k=0;
for(i=0;i<n;i++) rk[sa[i]]=i;
for(i=0;i<n-1;i++)
{
if(k) k--;
j=sa[rk[i]-1];
while(st[i+k]==st[j+k]) k++;
he[rk[i]]=k;
}
}
int main()
{
int mid,i,k,tp,cnt;
ll base;
while(scanf("%d",&k),k)
{
scanf("%s",txt);
mid=n=strlen(txt);
txt[n]='$';
scanf("%s",txt+n+1);
n=strlen(txt)+1;
m=150,ans=tp=base=0;
getsa(txt);
gethe(txt);
for(i=1;i<n;i++)
{
if(he[i]<k)
tp=0,base=0;//tp为栈顶。base为前面对后面的贡献
else
{
cnt=0;//统计有多少A的后缀是以he[i]为最小值的。
if(sa[i-1]<mid)//注意这里必须为sa[i-1]。因为he[i]为i和i-1的最长公共前缀。所以只能确定sa[i-1]与后面的关系
cnt++,base+=he[i]-k+1;
while(tp&&he[i]<q[tp-1])
{
base-=(q[tp-1]-he[i])*num[tp-1];//最小值改变。对后面的b产生影响
cnt+=num[tp-1];
tp--;
}
q[tp]=he[i];
num[tp++]=cnt;
if(sa[i]>mid)
ans+=base;
}
}
tp=0,base=0;
for(i=1;i<n;i++)
{
if(he[i]<k)
tp=0,base=0;
else
{
cnt=0;
if(sa[i-1]>mid)
cnt++,base+=he[i]-k+1;
while(tp&&he[i]<q[tp-1])
{
base-=(q[tp-1]-he[i])*num[tp-1];
cnt+=num[tp-1];
tp--;
}
q[tp]=he[i];
num[tp++]=cnt;
if(sa[i]<mid)
ans+=base;
}
}
printf("%I64d\n",ans);
}
return 0;
}