Genes in DNA
题意
给一个文本串和一个模式串,求模式串的所有前缀与后缀的组合在文本串里出现次数和。
当我觉得我懂KMP的时候,KMP给了我一巴掌:不,你不懂!
思路
这题朴素的算法就是把模式串的前缀和后缀组合列出来,跑KMP求出现次数和。显然数据范围不允许,那考虑求贡献的方法,对于文本串中第i个字符,把所有模式串中以这个字符为结尾的前缀 的个数求出来,记为ans1[i],注意,这里的以第i个字符结尾的前缀不是真的 以字符t[i]结尾的前缀,而是类似文本串与模式串kmp匹配时与t[i]匹配,假设某个前缀的长度为len,也就是s[0]…s[len-1]与t[i - len + 1]…t[i]匹配,这样的前缀有ans1[i]个,同样的,把文本串中以第i个字符为开头的后缀ans2[i]求出来,答案就是所有相邻两个位置的前缀数量*后缀数量之和。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
char s1[maxn], s2[maxn];
int nex[maxn], pre[maxn], suf[maxn], ans1[maxn], ans2[maxn];
void getnext(char *s, int d[]) {
int len = strlen(s);
nex[0] = -1;
int i = 0, j = -1;
while (i < len) {
if(j == -1 || s[i] == s[j]) {
i++; j++;
nex[i] = j; //这里判断了如果s[i],s[j]相等,表示有相同前缀后缀,然后i++,j++了
d[i] = d[j] + 1;//所以d[i]表示的是以编号为i-1的字符为结尾的前缀的个数,d[i]=d[j]+1的原因是
}//以编号i-1的*字符*为结尾的前缀的个数原来就有d[j]个,再加上以i-1为*结尾*的前缀的这个字符串,所以是+1个
else j = nex[j];
}
}
void kmp(char *t, char *s, int ans[], int p[]) {//文本串 模式串
int lent = strlen(t), lens = strlen(s), j = 0, i = 0, num = 0;
while (i < lent && j < lens) {
if(j == -1 || t[i] == s[j]) {
i++; j++;
ans[i - 1] = p[j] - (j == lens);//t[i],s[j]匹配的时候,就把s串以i字符为结尾的前缀传给t,表示s的前缀中以i-1
}//这个字符为结尾的前缀个数,j==lens的时候要-1是因为题目要求前缀不能是原串
else j = nex[j];
if(j == lens) {
j = nex[j];//要把t串更新完,所以如果s串匹配完了还要继续跑
}
}
}
int main()
{
int t, Case = 1;
scanf("%d", &t);
while (t--) {
scanf("%s%s", s1, s2);
getnext(s2, pre);//预处理出s串中前缀的数量pre
int len2 = strlen(s2), len1 = strlen(s1);
kmp(s1, s2, ans1, pre);
//倒过来求后缀数量
reverse(s1, s1 + len1);
reverse(s2, s2 + len2);
getnext(s2, suf);
kmp(s1, s2, ans2, suf);
ll ans = 0;
reverse(ans2, ans2 + len1);//倒过来好算答案
for (int i = 0; i < len1 - 1; i++)
ans += (ll)ans1[i] * ans2[i + 1];//答案就是t串中相邻的两个字符为结尾的前缀的个数*为开头的后缀的个数
printf("Case %d: %lld\n", Case++, ans);
}
return 0;
}