又一个DNA
【题意】
加里敦大学的生物研究所,发现了决定人喜不喜欢吃藕的基因序列S,有这个序列的碱基序列就会表现出喜欢吃藕的性状,但是研究人员发现对碱基序列S,任意修改其中不超过3个碱基,依然能够表现出吃藕的性状。现在研究人员想知道这个基因在DNA链S0上的位置。所以你需要统计在一个表现出吃藕性状的人的DNA序列S0上,有多少个连续子 串可能是该基因,即有多少个S0的连续子串修改小于等于三个字母能够变成S。
字符串长度
≤
1
0
5
\leq 10^5
≤105
【分析】
发现我们需要关注的不同位置最多只有三个。也就是说S在S0中最多被分为4段。那么我们只需要判断:对于所有连续的子串,给它们三次机会,能否成功与S匹配。
不难把这个问题转化为求三次最长公共前缀(LCP)。其实可以用后缀数组处理,但是我不会。
换个思路。发现由于公共前缀的长度满足单调性,于是想到用二分查找,然后判断字符串是否相等即可。我们可以用字符串哈希解决这个问题。
预处理+枚举子串
O
(
n
)
O(n)
O(n),二分
O
(
l
o
g
n
)
O(logn)
O(logn),判断相等
O
(
1
)
O(1)
O(1),于是时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
【代码】
/*其实可以模大质数而非自然溢出来降低冲突率,然而常数太大过不了*/
#include<bits/stdc++.h>
#define ll unsigned long long
using namespace std;
const int mn = 100005, v1 = 11, v2 = 13;
char a[mn], b[mn], v[] = {'A', 'C', 'G', 'T'};
ll s1[mn], t1[mn], q1[mn];
ll s2[mn], t2[mn], q2[mn];
int n, m;
inline ll query(int l, int r, ll* s, ll* p) {return s[r] - s[l - 1] * p[r - l + 1];}
inline int getlcp(int p1, int p2)
{
int l = 1, r = m, ret = 0;
while(l <= r)
{
int mid = (l + r) >> 1;
if(query(p1, p1 + mid - 1, s1, q1) == query(p2, p2 + mid - 1, t1, q1) && query(p1, p1 + mid - 1, s2, q2) == query(p2, p2 + mid - 1, t2, q2))
ret = mid, l = mid + 1;
else
r = mid - 1;
}
return ret;
}
inline bool check(int l)
{
int r = l + m - 1, now = 1;
for(int i = 0; i < 3; i++)
{
int len = getlcp(l, now);
now += len + 1; l += len + 1;
if(now > m) return 1;
}
return query(l, r, s1, q1) == query(now, m, t1, q1) && query(l, r, s2, q2) == query(now, m, t2, q2);
}
int main()
{
int T;
scanf("%d", &T), q1[0] = q2[0] = 1;
for(int i = 1; i <= mn - 5; i++)
q1[i] = q1[i - 1] * v1, q2[i] = q2[i - 1] * v2;
while(T--)
{
scanf("%s%s", a + 1, b + 1);
n = strlen(a + 1), m = strlen(b + 1);
for(int i = 1; i <= n; i++)
for(int j = 0; j < 4; j++)
if(a[i] == v[j])
a[i] = j;
for(int i = 1; i <= m; i++)
for(int j = 0; j < 4; j++)
if(b[i] == v[j])
b[i] = j;
s1[1] = s2[1] = a[1], t1[1] = t2[1] = b[1];
for(int i = 2; i <= n; i++)
s1[i] = s1[i - 1] * v1 + a[i], s2[i] = s2[i - 1] * v2 + a[i];
for(int i = 2; i <= m; i++)
t1[i] = t1[i - 1] * v1 + b[i], t2[i] = t2[i - 1] * v2 + b[i];
int ans = 0;
for(int i = 1; i + m - 1 <= n; i++)
ans += check(i);
printf("%d\n", ans);
}
}