题目:
题意:
给定两个字符串 A 和 B ,求长度不小于 k 的公共子串的个数(可以相同)。
题解:
首先我们知道:公共子串就是计算A的某个后缀与B的某个后缀的最长公共前缀,如果长度L大于k,则加上L-k+1组。
我们先回忆一下求最长公共前缀的思路:height区间取min值
那么我们有一个暴力的思路:
将两个字符串连接起来,中间用一个没有出现的字符分开。
然后通过height数组分组,某个组内的height都是大于等于k的,也就是任意两个后缀的最长公共前缀都至少为k。
扫描一遍,遇到一个B的后缀就与之前的A后缀进行统计,求出所有的满足的组数;然后再反过来:遇到A的后缀就和前面的B后缀进行统计,这样合起来就是全部的答案,但是这样的做法是
O(n2)
的。
可以发现两个后缀的最长公共前缀为这一段的height值的最小值。
可以通过递增的单调栈维护一下【当前height值】和【“被卡在这一层”即和后面的height值取min为当前height值的元素个数】,逐渐合并,均摊可以达到
O(n)
的复杂度。
我们要统计个数的时候,先把目前的height-k+1加上,如果要出栈的话,意味着height值要变小了,那我们公共子串的长度就减小了,那么这一层所有个数的贡献值都小了,全都要减
然后和上面相同扫描两遍即可
注意:
我们平常模板里的n都是不能用上的,相当于虚位,所以在这种n要被循环到的时候,n++
tot和sum都要开longlong
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=200005;
int c[N],height[N],rank[N],sa[N],y[N],x[N],m,n,stack[N][2];
char st[N],s[N];
void build_sa()
{
m=300;
for (int i=0;i<m;i++) c[i]=0;
for (int i=0;i<n;i++) c[x[i]=s[i]]++;
for (int i=1;i<m;i++) c[i]+=c[i-1];
for (int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for (int k=1;k<=n;k<<=1)
{
int p=0;
for (int i=n-k;i<n;i++) y[p++]=i;
for (int i=0;i<n;i++) if (sa[i]>=k) y[p++]=sa[i]-k;
for (int i=0;i<m;i++) c[i]=0;
for (int i=0;i<n;i++) c[x[y[i]]]++;
for (int i=1;i<m;i++) c[i]+=c[i-1];
for (int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);p=1;x[sa[0]]=0;
for (int i=1;i<n;i++) x[sa[i]]=y[sa[i]]==y[sa[i-1]] && ((sa[i]+k>=n?-1:y[sa[i]+k])==(sa[i-1]+k>=n?-1:y[sa[i-1]+k]))?p-1:p++;
if (p>n) break;
m=p;
}
}
void build_height()
{
for (int i=0;i<n;i++) rank[sa[i]]=i;
int k=0;height[0]=0;
for (int i=0;i<n;i++)
{
if (!rank[i]) continue;
if (k) k--;
int j=sa[rank[i]-1];
while (i+k<n && j+k<n && s[i+k]==s[j+k]) k++;
height[rank[i]]=k;
}
}
int main()
{
int k,nn;
while (scanf("%d",&k) && k)
{
scanf("%s",s); n=strlen(s);
scanf("%s",st); nn=strlen(st);
s[n]='&';int l=n;
for (int i=0;i<nn;i++) s[++n]=st[i];
s[++n]='\0';
build_sa();
build_height();
long long sum=0,tot=0;
int top=0;
for (int i=0;i<n;i++)
if (height[i]<k) tot=top=0;
else
{
int cnt=0;
if (sa[i-1]<l) cnt++,tot+=height[i]-k+1;
while (top>0 && height[i]<=stack[top][0])//0为height 1为包含A的个数
{
tot-=stack[top][1]*(stack[top][0]-height[i]);//height想要变小
cnt+=stack[top][1];
top--;
}
stack[++top][0]=height[i]; stack[top][1]=cnt;
if (sa[i]>l) sum+=tot;
}
top=0,tot=0;
for (int i=0;i<n;i++)
if (height[i]<k) tot=top=0;
else
{
int cnt=0;
if (sa[i-1]>l) cnt++,tot+=height[i]-k+1;
while (top>0 && height[i]<=stack[top][0])
{
tot-=stack[top][1]*(stack[top][0]-height[i]);
cnt+=stack[top][1];
top--;
}
stack[++top][0]=height[i]; stack[top][1]=cnt;
if (sa[i]<l) sum+=tot;
}
printf("%lld\n",sum);
}
}

本文介绍了一种高效的字符串匹配算法,用于寻找两个字符串之间的公共子串。该算法通过构建特殊的数据结构并利用单调栈来优化计算过程,实现了O(n)的时间复杂度。
2989

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



