查询字符串

查询字符串

给定 $n$ 个字符串 $f_{1},f_{2}, \dots ,f_{n}$。

这些字符串两两不同。

下面给定 $q$ 个询问。

其中,第 $i$ 次询问给定一个字符串 $s_{i}$,你的任务是:

  1. 计算 $f_{1} \sim f_{n}$ 这 $n$ 个字符串中,包含 $s_{i}$ 作为子串的字符串的数量。
  2. 从 $f_{1} \sim f_{n}$ 这 $n$ 个字符串中,任选一个包含 $s_{i}$ 作为子串的字符串输出。

输入格式

第一行包含整数 $n$。

接下来 $n$ 行,其中第 $i$ 行包含字符串 $f_{i}$。

再一行包含整数 $q$。

接下来 $q$ 行,其中第 $i$ 行包含字符串 $s_{i}$。

所有 $f_{i}$ 和 $s_{i}$ 都只包含小写字母、数字以及 . 。

输出格式

共 $q$ 行,其中第 $i$ 行输出第 $i$ 个询问的答案。

首先输出 $f_{1} \sim f_{n}$ 这 $n$ 个字符串中包含 $s_{i}$ 作为子串的字符串的数量。

然后从 $f_{1} \sim f_{n}$ 这 $n$ 个字符串中任选一个包含 $s_{i}$ 作为子串的字符串输出。

如果这样的字符串不唯一,则输出任意合理字符串均可,如果这样的字符串不存在,则输出 - 。

数据范围

前三个测试点满足 $1 \leq {n,q} \leq 20$。
所有测试点满足 $1 \leq n \leq 10000$,$1 \leq q \leq 50000$,$1 \leq \left| f_{i} \right| ,\left| s_{i} \right| \leq 8$。

输入样例:

4
test
contests
test.
.test
6
ts
.
st.
.test
contes.
st

输出样例:

1 contests
2 .test
1 test.
1 .test
0 -
4 test.

 

解题思路

  这题并没有很复杂,突破口是字符串长度的数据范围,每个字符串最大的出度不超过$8$。因为题目查询的是某个子串的出现次数,所以我们可以想到,因为每个字符串的长度很小,因此字符串对应的字串数量也很少(假设字符串的长度为$n$,那么这个字符串包含的子串个数为$1 + 2 + \dots + n = \frac{n \times \left( {1 + n} \right)}{2}$,如果$n=8$,最多也只有$36$个子串)。我们可以把每个字符串的子串存下来,查询的时候在这些子串中查找。

  因此我们可以开两个哈希表,一个用来统计对于某个子串有多少个字符串包含这个子串,另一个用来记录某个子串对应哪个字符串(任意一个包含这个子串的字符串)。

  AC代码如下:

 1 #include <cstdio>
 2 #include <unordered_map>
 3 #include <unordered_set>
 4 #include <string>
 5 #include <algorithm>
 6 using namespace std;
 7 
 8 unordered_map<string, int> cnt;
 9 unordered_map<string, string> mp;
10 
11 int main() {
12     int n;
13     scanf("%d", &n);
14     for (int i = 0; i < n; i++) {
15         char s[10];
16         scanf("%s", s);
17         
18         string str = s;
19         unordered_set<string> st;   // 用来判重,如果某个字符串包含多个相同的子串,只记录一次。例如:aaaaa
20         for (int len = 0; s[len]; len++) {
21             for (int i = 0; s[i + len]; i++) {
22                 string t = str.substr(i, len + 1);
23                 if (!st.count(t)) {
24                     cnt[t]++;
25                     mp[t] = str;
26                     st.insert(t);
27                 }
28             }
29         }
30     }
31     
32     int m;
33     scanf("%d", &m);
34     while (m--) {
35         char s[10];
36         scanf("%s", s);
37         printf("%d %s\n", cnt[s], mp.count(s) ? mp[s].c_str() : "-");
38     }
39     
40     return 0;
41 }

  因为是查找字符串,我们也可以用trie把所有的子串存下来,然后进行查找。

  AC代码如下:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N = 5e5 + 10, M = 1 << 8; // 粗略计算,一个字符串的所有子串最多会用到50个字符
 7 
 8 int trie[N][M], cnt[N], idx;
 9 char mp[N][10];
10 
11 void insert(char *s, char *str) {
12     int p = 0;
13     for (int i = 0; s[i]; i++) {
14         int t = s[i];
15         if (trie[p][t] == 0) trie[p][t] = ++idx;
16         p = trie[p][t];
17     }
18     
19     cnt[p]++;
20     strcpy(mp[p], str);
21 }
22 
23 int query(string s) {
24     int p = 0;
25     for (int i = 0; s[i]; i++) {
26         int t = s[i];
27         if (trie[p][t] == 0) return 0;
28         p = trie[p][t];
29     }
30     
31     return p;
32 }
33 
34 int main() {
35     int n;
36     scanf("%d", &n);
37     while (n--) {
38         char str[10];
39         scanf("%s", str);
40         
41         for (int i = 0; str[i]; i++) {
42             for (int j = 0; j <= i; j++) {
43                 char s[10] = {0};   // 记得初始化0,memcpy函数后面不会补0
44                 memcpy(s, str + j, i - j + 1);
45                 if (strcmp(mp[query(s)], str)) insert(s, str);  // 对于当前的字符串,如果某个子串出现过,那么就不可以再统计这个字符串了
46             }
47         }
48     }
49     
50     int m;
51     scanf("%d", &m);
52     while (m--) {
53         char str[10];
54         scanf("%s", str);
55         
56         int t = query(str);
57         if (cnt[t]) printf("%d %s\n", cnt[t], mp[t]);
58         else printf("0 -\n");
59     }
60     
61     return 0;
62 }

 

参考资料

  AcWing 4398. 查询字符串(AcWing杯 - 周赛):https://www.acwing.com/video/3789/

posted @ 2022-04-13 21:42  onlyblues  阅读(82)  评论(0编辑  收藏  举报
Web Analytics