出现次数

出现次数

给定一个长度为 n 的字符串 S=s1s2sn 以及一个长度为 m 的字符串 T=t1t2tm

两个字符串都由小写字母构成。

s[l,r] 来表示字符串 S 的子串 slsl+1sr

q 个询问,每个询问给出两个整数 li,ri1lirin),请你计算字符串 Ts[li,ri] 中作为子串出现了多少次。

例如,字符串 abacabadabacaba 中共包含 4 个子串 ba ,所以 ba 在 abacabadabacaba 中作为子串出现了 4 次。

输入格式

第一行包含三个整数 n,m,q

第二行包含一个长度为 n 的由小写字母构成的字符串 S

第三行包含一个长度为 m 的由小写字母构成的字符串 T

接下来 q 行,每行包含两个整数 li,ri

输出格式

每个询问输出一行答案,一个整数,表示出现次数。

数据范围

前三个测试点满足 1n,m,q20
所有测试点满足 1n,m10001q1051lirin

输入样例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(q×n),达到O(108)级别,能不能过很悬,但最后还是过了。

  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串的[l,r]这个区间内,由于模式串t的长度固定为m,因此我们可以用终点的下标来表示t,终点确定,t也就确定。所以如果t想出现在[l,r]内,那么t终点的下标要出现在[l+m1,r]中。

  所以我们要求的是,在[l+m1,r]中,遍历每一个下标,以这个下标为终点且长度为m的字符串有多少个是等于t的,因此可以用前缀和的思想。

  前缀和数组s[i]表示在s1i中,有多少个长度为m且等于t的字符串的数量。s[r]s[l+m11]就表示在[l+m1,r]中,以这些下标为终点,长度为m且等于t的字符串的个数。

  这种做法的时间复杂度是O(n2)的。

  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 @   onlyblues  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2021-03-13 图解带头节点的单链表的反转操作
Web Analytics
点击右上角即可分享
微信分享提示