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);
}
}
本文详细解析了POJ3415公共子串问题的解决思路,介绍了通过计算两字符串所有后缀间的最长公共前缀来找出长度不小于k的公共子串个数的方法。使用倍增算法和dc3算法加速后缀数组计算,并通过单调栈维护最长公共前缀长度。
2354

被折叠的 条评论
为什么被折叠?



