[关闭]
@Dmaxiya 2020-08-25T00:40:52.000000Z 字数 8966 阅读 1096

字符串练习题解

Hello_World


kmp

先来简单说一下 吧,感觉例题说得有点复杂了,这里写一下自己对 的一些理解吧。
还是用例题中给出的那两个串:

原串:
模式串:

当我们比较到模式串的第 个字符 的时候,发现后一个字符不能再匹配,所以我们需要一个幅度比较大的移动(直接说 ,不从一位一位如何移动过渡了)。

  1. 这个幅度比较大的移动要保证:之前匹配过的那些字符可以不用再匹配的长度最大。
  2. 那么,可以想到,如果对于当前模式串已经匹配过的前缀(),如果模式串的某个前缀 满足:它是当前前缀 的最长的后缀(且不等于 )。就可以将: 的后缀用 替换,从而将模式串往后移动一个比较大的幅度。

现在可以发现, 的最长的能用前缀表示的后缀是 ),那么在发现不匹配后,就可以将 移动到 的位置上(因为它们相等,且 已经和原串匹配了这三个字符),于是匹配的对应位置就变成了:

原串:
模式串:

现在要解决的问题就是:如何在模式串与原串不匹配的时候,使模式串快速改变匹配的位置。
其实从上面的描述可以看出,这个改变的位置,就是模式串当前失配(不匹配,以后都叫失配)位置前一个字符对应的前缀字符串的最大后缀(这个后缀能够等于模式串的某一个前缀),只要处理出这样一个数组就行了,这个数组就是“ 数组(失配数组)”,这个 数组的第 位,存的就是当前前缀 的最长的符合条件的前缀 的最后一个字符的下标。
例如上面的模式串的 数组就是(下标从 开始):

的核心就是 数组的定义,而不是如何在 的时间复杂度内求出模式串与原串是否匹配,能灵活运用 数组,才算是理解了 ,否则学会如何匹配字符串,只能算是学会了套 的板子,知道了 数组的定义,应该手写都能把 写出来了吧。
下面给出 的板子(来自板子-字符串-kmp):

  1. const int maxn = 1000 + 100;
  2. char str1[maxn], str2[maxn];
  3. int Next[maxn];
  4. // str1 为长串,str2 为短串,Next 为失配数组,maxn 为字符串长度
  5. // 字符串下标从 1 开始
  6. void get_next(char *s) {
  7. Next[1] = 0;
  8. int j = 0;
  9. for(int i = 2; s[i]; ++i) {
  10. while(j > 0 && s[i] != s[j + 1]) {
  11. j = Next[j];
  12. }
  13. if(s[i] == s[j + 1]) {
  14. ++j;
  15. }
  16. Next[i] = j;
  17. }
  18. }
  19. int KMP(char *s1, char *s2) {
  20. int j = 0, ret = 0;
  21. for(int i = 1; s1[i]; ++i) {
  22. while(j > 0 && s1[i] != s2[j + 1]) {
  23. j = Next[j];
  24. }
  25. if(s1[i] == s2[j + 1]) {
  26. ++j;
  27. }
  28. if(s2[j + 1] == '\0') {
  29. ++ret;
  30. j = Next[j];
  31. }
  32. }
  33. return ret;
  34. }

2 月 24 日 kmp & 最长公共子串练习题解

A. KMP算法

题解

裸题。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cstring>
  7. #include <string>
  8. #include <vector>
  9. #include <list>
  10. #include <queue>
  11. #include <stack>
  12. #include <map>
  13. #include <set>
  14. #include <bitset>
  15. #include <algorithm>
  16. #include <functional>
  17. #include <iomanip>
  18. using namespace std;
  19. #define LL long long
  20. const int maxn = 1000000 + 100;
  21. int T;
  22. int Next[maxn];
  23. char str1[maxn], str2[maxn];
  24. void get_next(char *str) {
  25. int j = 0;
  26. for(int i = 2; str[i]; ++i) {
  27. while(j != 0 && str[i] != str[j + 1]) {
  28. j = Next[j];
  29. }
  30. if(str[i] == str[j + 1]) {
  31. ++j;
  32. }
  33. Next[i] = j;
  34. }
  35. }
  36. int kmp(char *str1, char *str2) {
  37. int j = 0;
  38. int ret = 0;
  39. for(int i = 1; str1[i]; ++i) {
  40. while(j != 0 && str1[i] != str2[j + 1]) {
  41. j = Next[j];
  42. }
  43. if(str1[i] == str2[j + 1]) {
  44. ++j;
  45. }
  46. if(str2[j + 1] == '\0') {
  47. j = Next[j];
  48. ++ret;
  49. }
  50. }
  51. return ret;
  52. }
  53. int main() {
  54. #ifdef LOCAL
  55. freopen("test.txt", "r", stdin);
  56. // freopen("out.txt", "w", stdout);
  57. #endif // LOCAL
  58. ios::sync_with_stdio(false);
  59. scanf("%d", &T);
  60. while(T--) {
  61. scanf("%s%s", str1 + 1, str2 + 1);
  62. get_next(str1);
  63. printf("%d\n", kmp(str2, str1));
  64. }
  65. return 0;
  66. }

B. Count the string

题意

给定一个长度为 的字符串,询问这个字符串中,每个前缀出现的次数。

数据范围

题解

这题就是考 数组的定义与应用,虽然这种应用已经算是一种套路了,在 自动机上也这样考过,就是计算每个前缀对答案的贡献。
最开始每个前缀对答案的贡献都是 ,如果某个前缀的后缀(就是 数组指向的下标)是原字符串的前缀,那么这个前缀就对答案又有了一次贡献,最后将贡献加起来即可。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cstring>
  7. #include <string>
  8. #include <vector>
  9. #include <list>
  10. #include <queue>
  11. #include <stack>
  12. #include <map>
  13. #include <set>
  14. #include <bitset>
  15. #include <algorithm>
  16. #include <functional>
  17. #include <iomanip>
  18. using namespace std;
  19. #define LL long long
  20. const int MOD = 10007;
  21. const int maxn = 200000 + 100;
  22. int T, n;
  23. int Next[maxn];
  24. char str[maxn];
  25. int sum[maxn];
  26. void get_next(char *str) {
  27. int j = 0;
  28. for(int i = 2; str[i]; ++i) {
  29. while(j != 0 && str[i] != str[j + 1]) {
  30. j = Next[j];
  31. }
  32. if(str[i] == str[j + 1]) {
  33. ++j;
  34. }
  35. Next[i] = j;
  36. }
  37. }
  38. int main() {
  39. #ifdef LOCAL
  40. freopen("test.txt", "r", stdin);
  41. // freopen("out.txt", "w", stdout);
  42. #endif // LOCAL
  43. ios::sync_with_stdio(false);
  44. scanf("%d", &T);
  45. while(T--) {
  46. scanf("%d%s", &n, str + 1);
  47. get_next(str);
  48. memset(sum, 0, sizeof(int) * (n + 1));
  49. for(int i = n; i > 0; --i) {
  50. ++sum[i];
  51. sum[Next[i]] += sum[i];
  52. sum[Next[i]] %= MOD;
  53. }
  54. int ans = 0;
  55. for(int i = 1; i <= n; ++i) {
  56. ans = (ans + sum[i]) % MOD;
  57. }
  58. printf("%d\n", ans);
  59. }
  60. return 0;
  61. }

C. Common Subsequence

题意

求两个字符串的最长公共子序列。

数据范围

题解

这题原来的求最长公共子序列,不是求最长公共子串,而且还没给数据范围,出题失误,出题失误。但是两种的 思路是类似的,就是定义 为第一个字符串到第 个字符所代表的前缀,与第二个字符串到第 位所代表的前缀,的最长公共子序列的长度,这样就能得到下面的状态转移方程:

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cstring>
  7. #include <string>
  8. #include <vector>
  9. #include <list>
  10. #include <queue>
  11. #include <stack>
  12. #include <map>
  13. #include <set>
  14. #include <bitset>
  15. #include <algorithm>
  16. #include <functional>
  17. #include <iomanip>
  18. using namespace std;
  19. #define LL long long
  20. const int maxn = 1000 + 100;
  21. char str1[maxn], str2[maxn];
  22. int dp[maxn][maxn];
  23. int main() {
  24. #ifdef LOCAL
  25. freopen("test.txt", "r", stdin);
  26. // freopen("out.txt", "w", stdout);
  27. #endif // LOCAL
  28. ios::sync_with_stdio(false);
  29. while(scanf("%s%s", str1 + 1, str2 + 1) != EOF) {
  30. int len1 = strlen(str1 + 1);
  31. int len2 = strlen(str2 + 1);
  32. for(int i = 1; i <= len1; ++i) {
  33. for(int j = 1; j <= len2; ++j) {
  34. if(str1[i] == str2[j]) {
  35. dp[i][j] = max(max(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1] + 1);
  36. } else {
  37. dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
  38. }
  39. }
  40. }
  41. printf("%d\n", dp[len1][len2]);
  42. }
  43. return 0;
  44. }

Trie

字典树,数据结构课上应该都讲过,可能有人也已经写过了,要学的应该也就板子了吧,看别人的写法哪里比较简洁明了之类的。
这里直接贴出板子了(来自板子-字符串-Trie/字典树):

  1. const int maxcnt = 1000000 + 100;
  2. const int Size = 30;
  3. // maxcnt 为树的节点总数,cnt 为当前节点总数
  4. // 节点下标范围为[1,cnt],root为1
  5. struct Trie {
  6. int root, cnt;
  7. int tree[maxcnt][Size];
  8. int val[maxcnt];
  9. int create() {
  10. ++cnt;
  11. memset(tree[cnt], 0, sizeof(tree[cnt]));
  12. val[cnt] = 0;
  13. return cnt;
  14. }
  15. void Init() {
  16. cnt = 0;
  17. root = create();
  18. }
  19. inline int id(const char &ch) {
  20. return ch - 'a';
  21. }
  22. void add(char *s, int v) {
  23. int pos = root;
  24. for(int i = 1; s[i]; ++i) {
  25. int w = id(s[i]);
  26. if(tree[pos][w] == 0) {
  27. tree[pos][w] = create();
  28. }
  29. pos = tree[pos][w];
  30. }
  31. val[pos] = v;
  32. }
  33. int query(char *s) {
  34. int pos = root;
  35. for(int i = 1; s[i]; ++i) {
  36. int w = id(s[i]);
  37. if(tree[pos][w] == 0) {
  38. break;
  39. }
  40. pos = tree[pos][w];
  41. }
  42. return val[pos];
  43. }
  44. };

比较常用的也就是“字母树”和 树了吧, 数用得可能更经常些,字母树没什么好考的,要考也考 自动机(就是在 上跑 ),如果碰到 树的话,大多数都涉及到数字的位运算,这个比较考思维一些,但其实板子写得好,理解了板子,思路清晰的话,应该也写得比较快。

2 月 25 日 回文串 & 字典树练习题解

A. Palindrome

题意

给定一个长度为 的字符串,问最少添加多少个字符,能够使得该字符串成为一个回文串。

数据范围

题解

这题比较考思维,想到了就很好写了,就是把原串反过来,然后求两个串的最长公共子序列长度,用总长度减去公共子序列长度就是答案。
还有注意一下这题卡空间,不能开 数组,用一下滚动数组就行了。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cstring>
  7. #include <string>
  8. #include <vector>
  9. #include <list>
  10. #include <queue>
  11. #include <stack>
  12. #include <map>
  13. #include <set>
  14. #include <bitset>
  15. #include <algorithm>
  16. #include <functional>
  17. #include <iomanip>
  18. using namespace std;
  19. #define LL long long
  20. const int maxn = 5000 + 100;
  21. int n;
  22. char str1[maxn], str2[maxn];
  23. int dp[2][maxn];
  24. int main() {
  25. #ifdef LOCAL
  26. freopen("test.txt", "r", stdin);
  27. // freopen("out.txt", "w", stdout);
  28. #endif // LOCAL
  29. ios::sync_with_stdio(false);
  30. while(scanf("%d", &n) != EOF) {
  31. scanf("%s", str1 + 1);
  32. int len = strlen(str1 + 1);
  33. for(int i = 1; i <= len; ++i) {
  34. str2[len - i + 1] = str1[i];
  35. }
  36. for(int i = 1; i <= len; ++i) {
  37. int ii = i % 2;
  38. int iii = (i + 1) % 2;
  39. for(int j = 1; j <= len; ++j) {
  40. if(str1[i] == str2[j]) {
  41. dp[ii][j] = max(max(dp[iii][j], dp[ii][j - 1]), dp[iii][j - 1] + 1);
  42. } else {
  43. dp[ii][j] = max(dp[iii][j], dp[ii][j - 1]);
  44. }
  45. }
  46. }
  47. printf("%d\n", len - dp[len % 2][len]);
  48. }
  49. return 0;
  50. }

B. Trie树

题解

裸题,就是这题说有 个字符串,但是每个字符串有 个字符,所以 的值应该为 (最大节点数,每一个节点为一个字母)。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cstring>
  7. #include <string>
  8. #include <vector>
  9. #include <list>
  10. #include <queue>
  11. #include <stack>
  12. #include <map>
  13. #include <set>
  14. #include <bitset>
  15. #include <algorithm>
  16. #include <functional>
  17. #include <iomanip>
  18. using namespace std;
  19. #define LL long long
  20. const int maxcnt = 1000000 + 100;
  21. const int Size = 30;
  22. struct Trie {
  23. int root, cnt;
  24. int tree[maxcnt][Size];
  25. int val[maxcnt];
  26. int create() {
  27. ++cnt;
  28. memset(tree[cnt], 0, sizeof(tree[cnt]));
  29. val[cnt] = 0;
  30. return cnt;
  31. }
  32. void Init() {
  33. cnt = 0;
  34. root = create();
  35. }
  36. inline int id(const char &ch) {
  37. return ch - 'a';
  38. }
  39. void add(char *s) {
  40. int pos = root;
  41. for(int i = 1; s[i]; ++i) {
  42. int w = id(s[i]);
  43. if(tree[pos][w] == 0) {
  44. tree[pos][w] = create();
  45. }
  46. pos = tree[pos][w];
  47. ++val[pos];
  48. }
  49. }
  50. int query(char *s) {
  51. int pos = root;
  52. for(int i = 1; s[i]; ++i) {
  53. int w = id(s[i]);
  54. if(tree[pos][w] == 0) {
  55. return 0;
  56. }
  57. pos = tree[pos][w];
  58. }
  59. return val[pos];
  60. }
  61. };
  62. int n, m;
  63. char str[15];
  64. Trie t;
  65. int main() {
  66. #ifdef LOCAL
  67. freopen("test.txt", "r", stdin);
  68. // freopen("out.txt", "w", stdout);
  69. #endif // LOCAL
  70. ios::sync_with_stdio(false);
  71. while(scanf("%d", &n) != EOF) {
  72. t.Init();
  73. for(int i = 0; i < n; ++i) {
  74. scanf("%s", str + 1);
  75. t.add(str);
  76. }
  77. scanf("%d", &m);
  78. for(int i = 0; i < m; ++i) {
  79. scanf("%s", str + 1);
  80. printf("%d\n", t.query(str));
  81. }
  82. }
  83. return 0;
  84. }

C. Xor Sum

题解

树,也算是裸题吧,就是把 个数字都放到 数中,然后对于每一次询问,从高位往低位贪心,如果存在某一位与 的相同位上的数字相反( 相反),就取那一位(这样那一位的异或值才为 ),如果不存在,则只能让那一位的异或值为 ,然后从高位到低位贪心。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cstring>
  7. #include <string>
  8. #include <vector>
  9. #include <list>
  10. #include <queue>
  11. #include <stack>
  12. #include <map>
  13. #include <set>
  14. #include <bitset>
  15. #include <algorithm>
  16. #include <functional>
  17. #include <iomanip>
  18. using namespace std;
  19. #define LL long long
  20. const int maxcnt = 3200000 + 100;
  21. const int Size = 2;
  22. struct Trie {
  23. int root, cnt;
  24. int tree[maxcnt][Size];
  25. int val[maxcnt];
  26. int create() {
  27. ++cnt;
  28. memset(tree[cnt], 0, sizeof(tree[cnt]));
  29. return cnt;
  30. }
  31. void Init() {
  32. cnt = 0;
  33. root = create();
  34. }
  35. int id(int num, int dig) {
  36. return ((num >> (32 - dig)) & 1);
  37. }
  38. void add(int num) {
  39. int pos = root;
  40. for(int i = 1; i <= 32; ++i) {
  41. int w = id(num, i);
  42. if(tree[pos][w] == 0) {
  43. tree[pos][w] = create();
  44. }
  45. pos = tree[pos][w];
  46. }
  47. }
  48. int query(int num) {
  49. int ret = 0;
  50. int pos = root;
  51. for(int i = 1; i <= 32; ++i) {
  52. int w = !id(num, i);
  53. if(tree[pos][w] == 0) {
  54. w = !w;
  55. }
  56. ret |= (w << (32 - i));
  57. pos = tree[pos][w];
  58. }
  59. return ret;
  60. }
  61. };
  62. int T, n, m;
  63. int num;
  64. Trie t;
  65. int main() {
  66. #ifdef LOCAL
  67. freopen("test.txt", "r", stdin);
  68. // freopen("out.txt", "w", stdout);
  69. #endif // LOCAL
  70. ios::sync_with_stdio(false);
  71. scanf("%d", &T);
  72. for(int cas = 1; cas <= T; ++cas) {
  73. printf("Case #%d:\n", cas);
  74. t.Init();
  75. scanf("%d%d", &n, &m);
  76. for(int i = 0; i < n; ++i) {
  77. scanf("%d", &num);
  78. t.add(num);
  79. }
  80. for(int i = 0; i < m; ++i) {
  81. scanf("%d", &num);
  82. printf("%d\n", t.query(num));
  83. }
  84. }
  85. return 0;
  86. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注