Common Substrings
Time Limit: 5000MS | Memory Limit: 65536K | |
Total Submissions: 8106 | Accepted: 2688 |
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
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
Source
POJ Monthly--2007.10.06, wintokk
题意是求两个字符串长度>=K的公共子串有多少个。
先考虑怎么求解公共子串的问题。公共子串可以用后缀数组求,将两个字符串str1和str2合并为一个字符串,中间插入一个不会出现的字符做分割。得到后缀数组和高度数组,有了高度数组就可以求出任意两个后缀的lcp最长公共前缀值,lcp即为两个后缀之间height[i]的最小值。
回到这个问题上,假如两个分别属于str1和str2的后缀lcp为X (X>K), 那么就计数X-K+1个长度>=K的公共子串。那么答案就是每个属于str1和每个属于str2的lcp-K+1求和。
直接枚举要n^2logn复杂度(单次查询两个后缀lcp用rmq--logn复杂度)。任意两个后缀的lcp值取决于它们之间的最小值,直接枚举慢就慢在每两个后缀都要找一遍它们之间的最小值。利用最小值这个关键点,假如枚举以第i个lcp值为最小值的区间[l,r],那么ans+=(左边属于str1的后缀个数*右边属于str2+左边属于str2*右边属于str1)*(lcp-K+1)。而找到以某个值为最小值的区间用单调队列左右扫一遍O(n)就可以得到。
题意是求两个字符串长度>=K的公共子串有多少个。
先考虑怎么求解公共子串的问题。公共子串可以用后缀数组求,将两个字符串str1和str2合并为一个字符串,中间插入一个不会出现的字符做分割。得到后缀数组和高度数组,有了高度数组就可以求出任意两个后缀的lcp最长公共前缀值,lcp即为两个后缀之间height[i]的最小值。
回到这个问题上,假如两个分别属于str1和str2的后缀lcp为X (X>K), 那么就计数X-K+1个长度>=K的公共子串。那么答案就是每个属于str1和每个属于str2的lcp-K+1求和。
直接枚举要n^2logn复杂度(单次查询两个后缀lcp用rmq--logn复杂度)。任意两个后缀的lcp值取决于它们之间的最小值,直接枚举慢就慢在每两个后缀都要找一遍它们之间的最小值。利用最小值这个关键点,假如枚举以第i个lcp值为最小值的区间[l,r],那么ans+=(左边属于str1的后缀个数*右边属于str2+左边属于str2*右边属于str1)*(lcp-K+1)。而找到以某个值为最小值的区间用单调队列左右扫一遍O(n)就可以得到。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=200005;
int t1[maxn], t2[maxn], c[maxn];
bool cmp(int *r, int a, int b, int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int str[], int sa[], int rank[], 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++) rank[sa[i]]=i;
for(i=0; i<n; i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k]) k++;
height[rank[i]]=k;
}
}
int n;
int rank[maxn], height[maxn];
char s[maxn];
int r[maxn];
int sa[maxn];
int que[maxn], head,tail;
int lb[maxn], rb[maxn];
int sum[maxn]={0};
int query(int l, int r)
{
return sum[r]-sum[l-1];
}
int main()
{
int K;
while(scanf("%d", &K)==1 &&K){
scanf("%s", s);
int len=strlen(s);
for(int i=0; i<len; i++) {r[i]=s[i];}
scanf("%s", s);
int len1=strlen(s);
for(int i=0; i<len1; i++) r[len+1+i]=s[i];
n=len+1+len1;
r[len]=1;
r[n]=0;
da(r,sa,rank, height, n,128);
head=0,tail=-1;
for(int i=1; i<=n; i++){
while(tail+1!=head && height[que[tail]]>height[i]) tail--;
if(tail+1!=head) lb[i]=que[tail]+1;
else lb[i]=1;
que[++tail]=i;
}
head=0, tail=-1;
for(int i=n; i>=1; i--){
while(tail+1!=head && height[que[tail]]>=height[i]) tail--;
if(tail+1!=head) rb[i]=que[tail]-1;
else rb[i]=n;
que[++tail]=i;
}
memset(sum, 0, sizeof(sum)); //前缀和维护区间内属于str1的后缀个数
for(int i=1; i<=n; i++){
if(sa[i]<len)
sum[i]++;
sum[i]+=sum[i-1];
}
long long ans=0;//注意答案可能大于int
for(int i=2; i<=n; i++){
if(height[i]<K) continue;
int l=lb[i]-1, r=rb[i];
int ltot=i-l, rtot=r-i+1;
ans+=(long long)query(l, i-1)*(rtot-query(i, r))*(height[i]-K+1);
ans+=(long long)(ltot-query(l,i-1))*query(i, r)*(height[i]-K+1);
}
printf("%lld\n", ans);
}
return 0;
}