POJ 3415 Common Substrings
题意:长度不小于k的公共子串的个数;
思路:基本思路是计算A的所有后缀和B的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于k的部分全部加起来。 先将两个字符串连起来, 中间用一个没有出现过的字符隔开。按height值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和。 扫描一遍, 每遇到一个B的后缀就统计与前面的A的后缀能产生多少个长度不小于k的公共子串, 这里A的后缀需要用一个单调的栈来高效的维护。 然后对A也这样做一次;
在这里提一下如何用单调栈维护,假如现在是维护A的后缀,找到一个A的后缀就将其入栈,但是如果将要入栈的这个的height值比栈里面的小则需要更新栈里面的的值,假如栈里面现在有两个值,分别为4 5,现在有一个值为3的要进栈,则需要将前两个的值更新为3,因为如果后面有B的后缀出现,则B与前面的A的后缀的最长公共前缀必定不会大于3;所以要更新为3,但是这样栈里就会有3个相同的值都为3,因此给每个值赋予一个权值代表里面有几个则可以批量更新,当遇到B后缀的时候记得也要维护一次单调栈,因为到B如果height值更小了,则结果将更小,维护完之后统计一下就好了;
/*
*
* 倍增算法(n*logn)
* 待排序数组长度为n,放在0~n-1中,在最后补0
* sa为后缀数组,把后缀从小到大排序把后缀开头存起来,rank为名次数组,以i开头的后缀在所有后缀中排第几
* sa的有效值为1~n,sa[0]必为n无效
* rank的有效值为0~n-1,rank[n]必为0无效
* height的有效值为2~n,前两个为0
*
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
using namespace std;
const int maxn=1e6+10;
int x[maxn*3];
int wa[maxn*3],wb[maxn*3],ww[maxn*3],wv[maxn*3],nn,Z=1,kk;
char str[100010];
int cmp(int *r,int a,int b,int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int *sa,int n,int m)//求的数组,得到的后缀数组,最长长度+1,数组里的最大值(一般180或者255);
{
int i,j,p,*x=wa,*y=wb,*t;
for(i=0; i<m; i++) ww[i]=0;
for(i=0; i<n; i++) ww[x[i]=r[i]]++;
for(i=1; i<m; i++) ww[i]+=ww[i-1];
for(i=n-1; i>=0; i--) sa[--ww[x[i]]]=i; //处理长度为一的字符串,得到sa数组
for(j=1,p=1; p<n; j*=2,m=p) //倍增法求sa
{
for(p=0,i=n-j; i<n; i++) y[p++]=i;
for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;//利用上次的sa直接求出按第二个关键字排序
for(i=0; i<n; i++) wv[i]=x[y[i]]; //第二关键字的排序得出第一关键字的顺序
for(i=0; i<m; i++) ww[i]=0;
for(i=0; i<n; i++) ww[wv[i]]++;
for(i=1; i<m; i++) ww[i]+=ww[i-1];
for(i=n-1; i>=0; i--) sa[--ww[wv[i]]]=y[i]; //根据第一关键字的顺序排出sa数组的顺序
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++) //更新x数组 x为rank数组
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
return ;
}
int c0(int *r,int a,int b)
{
return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];
}
int c12(int k,int *r,int a,int b)
{
if(k==2) return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
}
void Sort(int *r,int *a,int *b,int n,int m)
{
int i;
for(i=0; i<n; i++) wv[i]=r[a[i]];
for(i=0; i<m; i++) ww[i]=0;
for(i=0; i<n; i++) ww[wv[i]]++;
for(i=1; i<m; i++) ww[i]+=ww[i-1];
for(i=n-1; i>=0; i--) b[--ww[wv[i]]]=a[i];
return ;
}
void dc3(int *r,int *sa,int n,int m)//dc3比倍增法快一点
{
int i,j,*rn=r+n,*san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0; i<n; i++) if(i%3!=0) wa[tbc++]=i;
Sort(r+2,wa,wb,tbc,m);
Sort(r+1,wb,wa,tbc,m);
Sort(r,wa,wb,tbc,m);
for(p=1,rn[F(wb[0])]=0,i=1; i<tbc; i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc) dc3(rn,san,tbc,p);
else for(i=0; i<tbc; i++) san[rn[i]]=i;
for(i=0; i<tbc; i++) if(san[i]<tb) wb[ta++]=san[i]*3;
if(n%3==1) wb[ta++]=n-1;
Sort(r,wb,wa,ta,m);
for(i=0; i<tbc; i++) wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0; i<ta&&j<tbc; p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(; i<ta; p++) sa[p]=wa[i++];
for(; j<tbc; p++) sa[p]=wb[j++];
return ;
}
int h[maxn*3];//也就是排名相邻的两个后缀的最长公共前缀sa[i]和sa[i-1]
int Rank[maxn*3];//名次数组
void get_height(int *r,int *sa,int n)//同上,n小1
{
int k=0,i,j;
for(int i=1; i<=n; i++) Rank[sa[i]]=i;
for(int i=0; i<n; h[Rank[i++]]=k)
for(k?k--:0,j=sa[Rank[i]-1]; r[i+k]==r[j+k]; k++) ;
return ;
}
int a[maxn*3];
int sa[maxn*3],r[maxn*3];
int s[maxn][2];
int solve1(int len,int len1)
{
int n=len+len1+1;
long long sum=0,cont=0,top=0,tot=0;
for(int i=1;i<=n;i++)//维护a串
{
if(h[i]<kk) tot=0,top=0;//不能达到分组条件
else
{
cont=0;
if(sa[i-1]<len) cont++,tot+=h[i]-kk+1;//是a串的后缀
while(top>0&&h[i]<=s[top-1][0])//维护单调栈
{
top--;
tot-=s[top][1]*(s[top][0]-h[i]);//令小的值更新前面的,如果h[i]减小则前面提供给后面后面的b的后缀的子串也相应的减少
cont+=s[top][1];//累加前面的值用于统一更新
}
s[top][0]=h[i],s[top++][1]=cont;//a串后缀入栈,cont为1,b串后缀入队cont为0
if(sa[i]>len) sum+=tot;//b串的后缀,统计前面的结果
}
}
tot=0,top=0;
for(int i=1;i<=n;i++)//维护b串
{
if(h[i]<kk) tot=0,top=0;
else
{
cont=0;
if(sa[i-1]>len) cont++,tot+=h[i]-kk+1;
while(top>0&&h[i]<=s[top-1][0])
{
top--;
tot-=s[top][1]*(s[top][0]-h[i]);
cont+=s[top][1];
}
s[top][0]=h[i],s[top++][1]=cont;
if(sa[i]<len) sum+=tot;
}
}
printf("%lld\n",sum);
}
int main()
{
while(~scanf("%d",&kk)&&kk)
{
string a[2];
cin>>a[0]>>a[1];
int len=a[0].size();
for(int i=0;i<len;i++)
x[i]=a[0][i];
x[len]=1;
int len1=a[1].size();
for(int i=0;i<len1;i++)
x[1+i+len]=a[1][i];
x[len+len1+1]=0;
nn=len+len1+1;
da(x,sa,nn+1,255);
get_height(x,sa,nn);
solve1(len,len1);
}
}