出现次数

出现次数

给定一个长度为 $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/

posted @ 2022-03-13 10:26  onlyblues  阅读(56)  评论(0编辑  收藏  举报
Web Analytics