出现次数
出现次数
给定一个长度为 $n$ 的字符串 $S=s_{1}s_{2}…s_{n}$ 以及一个长度为 $m$ 的字符串 $T=t_{1}t_{2}…t_{m}$。
两个字符串都由小写字母构成。
用 $s \left[ {l,r} \right]$ 来表示字符串 $S$ 的子串 $s_{l}s_{l+1}…s_{r}$。
有 $q$ 个询问,每个询问给出两个整数 $l_{i},r_{i}$($1\leq l_{i} \leq r_{i} \leq n$),请你计算字符串 $T$ 在 $s \left[ {l_{i},r_{i}} \right]$ 中作为子串出现了多少次。
例如,字符串 abacabadabacaba 中共包含 $4$ 个子串 ba ,所以 ba 在 abacabadabacaba 中作为子串出现了 $4$ 次。
输入格式
第一行包含三个整数 $n,m,q$。
第二行包含一个长度为 $n$ 的由小写字母构成的字符串 $S$。
第三行包含一个长度为 $m$ 的由小写字母构成的字符串 $T$。
接下来 $q$ 行,每行包含两个整数 $l_{i},r_{i}$。
输出格式
每个询问输出一行答案,一个整数,表示出现次数。
数据范围
前三个测试点满足 $1 \leq n,m,q \leq 20$。
所有测试点满足 $1 \leq n,m \leq 1000,1 \leq q \leq {10}^{5},1 \leq l_{i} \leq r_{i} \leq n$。
输入样例1:
15 2 3 abacabadabacaba ba 1 15 3 4 2 14
输出样例1:
4 0 3
输入样例2:
3 5 2 aaa baaab 1 3 1 1
输出样例2:
0 0
解题思路
这题可以用kmp过,但时间复杂度是$O \left( q \times n \right)$,达到$O \left( {10}^{8} \right)$级别,能不能过很悬,但最后还是过了。
kmp写法的AC代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int N = 1010; 6 7 char str1[N], str2[N]; 8 int ne[N]; 9 10 int main() { 11 int n, m, tot; 12 scanf("%d %d %d", &n, &m, &tot); 13 scanf("%s %s", str1 + 1, str2 + 1); 14 15 for (int i = 2, j = 0; i <= m; i++) { 16 while (j && str2[i] != str2[j + 1]) { 17 j = ne[j]; 18 } 19 if (str2[i] == str2[j + 1]) j++; 20 ne[i] = j; 21 } 22 23 while (tot--) { 24 int l, r; 25 scanf("%d %d", &l, &r); 26 27 int ret = 0; 28 for (int i = l, j = 0; i <= r; i++) { 29 while (j && str1[i] != str2[j + 1]) { 30 j = ne[j]; 31 } 32 if (str1[i] == str2[j + 1]) j++; 33 if (j == m) { 34 ret++; 35 j = ne[j]; 36 } 37 } 38 39 printf("%d\n", ret); 40 } 41 42 return 0; 43 }
这里给出一种用前缀和的解法,这种解法完全没想到。前缀和不仅可以求和,还可以统计个数。
对于$s$串的$\left[ {l, r} \right]$这个区间内,由于模式串$t$的长度固定为$m$,因此我们可以用终点的下标来表示$t$,终点确定,$t$也就确定。所以如果$t$想出现在$\left[ {l, r} \right]$内,那么$t$终点的下标要出现在$\left[ {l+m-1, r} \right]$中。
所以我们要求的是,在$\left[ {l+m-1, r} \right]$中,遍历每一个下标,以这个下标为终点且长度为$m$的字符串有多少个是等于$t$的,因此可以用前缀和的思想。
前缀和数组$s \left[ i \right]$表示在$s$的$1 \sim i$中,有多少个长度为$m$且等于$t$的字符串的数量。$s \left[ r \right] - s \left[ l+m-1-1 \right]$就表示在$\left[ {l+m-1, r} \right]$中,以这些下标为终点,长度为$m$且等于$t$的字符串的个数。
这种做法的时间复杂度是$O \left( n^{2} \right)$的。
AC代码如下:
1 #include <cstdio> 2 #include <iostream> 3 #include <string> 4 #include <algorithm> 5 using namespace std; 6 7 const int N = 1010; 8 9 string str, t; 10 int s[N]; 11 12 int main() { 13 int n, m, tot; 14 cin >> n >> m >> tot; 15 cin >> str >> t; 16 17 str = ' ' + str; // str从下标1k开始 18 for (int i = m; i <= n; i++) { // 因为t的长度为m,所以至少从下标m开始,m之前的下标不可能会有t 19 s[i] = s[i - 1]; 20 if (str.substr(i - m + 1, m) == t) s[i]++; // 判断以i为终点,长度为m的字符串是否为t 21 } 22 23 while (tot--) { 24 int l, r; 25 cin >> l >> r; 26 l += m - 1; 27 cout << (l > r ? 0 : s[r] - s[l - 1]) << endl; // 要保证l <= r 28 } 29 30 return 0; 31 }
参考资料
AcWing 4312. 出现次数(AcWing杯 - 周赛):https://www.acwing.com/video/3729/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/15999706.html