@Dmaxiya
2020-08-24T16:40:52.000000Z
字数 8966
阅读 1294
Hello_World
先来简单说一下 吧,感觉例题说得有点复杂了,这里写一下自己对 的一些理解吧。
还是用例题中给出的那两个串:
原串:
模式串:
当我们比较到模式串的第 个字符 的时候,发现后一个字符不能再匹配,所以我们需要一个幅度比较大的移动(直接说 ,不从一位一位如何移动过渡了)。
现在可以发现, 的最长的能用前缀表示的后缀是 (),那么在发现不匹配后,就可以将 移动到 的位置上(因为它们相等,且 已经和原串匹配了这三个字符),于是匹配的对应位置就变成了:
原串:
模式串:
现在要解决的问题就是:如何在模式串与原串不匹配的时候,使模式串快速改变匹配的位置。
其实从上面的描述可以看出,这个改变的位置,就是模式串当前失配(不匹配,以后都叫失配)位置前一个字符对应的前缀字符串的最大后缀(这个后缀能够等于模式串的某一个前缀),只要处理出这样一个数组就行了,这个数组就是“ 数组(失配数组)”,这个 数组的第 位,存的就是当前前缀 的最长的符合条件的前缀 的最后一个字符的下标。
例如上面的模式串的 数组就是(下标从 开始):
的核心就是 数组的定义,而不是如何在 的时间复杂度内求出模式串与原串是否匹配,能灵活运用 数组,才算是理解了 ,否则学会如何匹配字符串,只能算是学会了套 的板子,知道了 数组的定义,应该手写都能把 写出来了吧。
下面给出 的板子(来自板子-字符串-kmp):
const int maxn = 1000 + 100;char str1[maxn], str2[maxn];int Next[maxn];// str1 为长串,str2 为短串,Next 为失配数组,maxn 为字符串长度// 字符串下标从 1 开始void get_next(char *s) {Next[1] = 0;int j = 0;for(int i = 2; s[i]; ++i) {while(j > 0 && s[i] != s[j + 1]) {j = Next[j];}if(s[i] == s[j + 1]) {++j;}Next[i] = j;}}int KMP(char *s1, char *s2) {int j = 0, ret = 0;for(int i = 1; s1[i]; ++i) {while(j > 0 && s1[i] != s2[j + 1]) {j = Next[j];}if(s1[i] == s2[j + 1]) {++j;}if(s2[j + 1] == '\0') {++ret;j = Next[j];}}return ret;}
裸题。
#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <climits>#include <cstring>#include <string>#include <vector>#include <list>#include <queue>#include <stack>#include <map>#include <set>#include <bitset>#include <algorithm>#include <functional>#include <iomanip>using namespace std;#define LL long longconst int maxn = 1000000 + 100;int T;int Next[maxn];char str1[maxn], str2[maxn];void get_next(char *str) {int j = 0;for(int i = 2; str[i]; ++i) {while(j != 0 && str[i] != str[j + 1]) {j = Next[j];}if(str[i] == str[j + 1]) {++j;}Next[i] = j;}}int kmp(char *str1, char *str2) {int j = 0;int ret = 0;for(int i = 1; str1[i]; ++i) {while(j != 0 && str1[i] != str2[j + 1]) {j = Next[j];}if(str1[i] == str2[j + 1]) {++j;}if(str2[j + 1] == '\0') {j = Next[j];++ret;}}return ret;}int main() {#ifdef LOCALfreopen("test.txt", "r", stdin);// freopen("out.txt", "w", stdout);#endif // LOCALios::sync_with_stdio(false);scanf("%d", &T);while(T--) {scanf("%s%s", str1 + 1, str2 + 1);get_next(str1);printf("%d\n", kmp(str2, str1));}return 0;}
给定一个长度为 的字符串,询问这个字符串中,每个前缀出现的次数。
这题就是考 数组的定义与应用,虽然这种应用已经算是一种套路了,在 自动机上也这样考过,就是计算每个前缀对答案的贡献。
最开始每个前缀对答案的贡献都是 ,如果某个前缀的后缀(就是 数组指向的下标)是原字符串的前缀,那么这个前缀就对答案又有了一次贡献,最后将贡献加起来即可。
#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <climits>#include <cstring>#include <string>#include <vector>#include <list>#include <queue>#include <stack>#include <map>#include <set>#include <bitset>#include <algorithm>#include <functional>#include <iomanip>using namespace std;#define LL long longconst int MOD = 10007;const int maxn = 200000 + 100;int T, n;int Next[maxn];char str[maxn];int sum[maxn];void get_next(char *str) {int j = 0;for(int i = 2; str[i]; ++i) {while(j != 0 && str[i] != str[j + 1]) {j = Next[j];}if(str[i] == str[j + 1]) {++j;}Next[i] = j;}}int main() {#ifdef LOCALfreopen("test.txt", "r", stdin);// freopen("out.txt", "w", stdout);#endif // LOCALios::sync_with_stdio(false);scanf("%d", &T);while(T--) {scanf("%d%s", &n, str + 1);get_next(str);memset(sum, 0, sizeof(int) * (n + 1));for(int i = n; i > 0; --i) {++sum[i];sum[Next[i]] += sum[i];sum[Next[i]] %= MOD;}int ans = 0;for(int i = 1; i <= n; ++i) {ans = (ans + sum[i]) % MOD;}printf("%d\n", ans);}return 0;}
求两个字符串的最长公共子序列。
这题原来的求最长公共子序列,不是求最长公共子串,而且还没给数据范围,出题失误,出题失误。但是两种的 思路是类似的,就是定义 为第一个字符串到第 个字符所代表的前缀,与第二个字符串到第 位所代表的前缀,的最长公共子序列的长度,这样就能得到下面的状态转移方程:
#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <climits>#include <cstring>#include <string>#include <vector>#include <list>#include <queue>#include <stack>#include <map>#include <set>#include <bitset>#include <algorithm>#include <functional>#include <iomanip>using namespace std;#define LL long longconst int maxn = 1000 + 100;char str1[maxn], str2[maxn];int dp[maxn][maxn];int main() {#ifdef LOCALfreopen("test.txt", "r", stdin);// freopen("out.txt", "w", stdout);#endif // LOCALios::sync_with_stdio(false);while(scanf("%s%s", str1 + 1, str2 + 1) != EOF) {int len1 = strlen(str1 + 1);int len2 = strlen(str2 + 1);for(int i = 1; i <= len1; ++i) {for(int j = 1; j <= len2; ++j) {if(str1[i] == str2[j]) {dp[i][j] = max(max(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1] + 1);} else {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}}}printf("%d\n", dp[len1][len2]);}return 0;}
字典树,数据结构课上应该都讲过,可能有人也已经写过了,要学的应该也就板子了吧,看别人的写法哪里比较简洁明了之类的。
这里直接贴出板子了(来自板子-字符串-Trie/字典树):
const int maxcnt = 1000000 + 100;const int Size = 30;// maxcnt 为树的节点总数,cnt 为当前节点总数// 节点下标范围为[1,cnt],root为1struct Trie {int root, cnt;int tree[maxcnt][Size];int val[maxcnt];int create() {++cnt;memset(tree[cnt], 0, sizeof(tree[cnt]));val[cnt] = 0;return cnt;}void Init() {cnt = 0;root = create();}inline int id(const char &ch) {return ch - 'a';}void add(char *s, int v) {int pos = root;for(int i = 1; s[i]; ++i) {int w = id(s[i]);if(tree[pos][w] == 0) {tree[pos][w] = create();}pos = tree[pos][w];}val[pos] = v;}int query(char *s) {int pos = root;for(int i = 1; s[i]; ++i) {int w = id(s[i]);if(tree[pos][w] == 0) {break;}pos = tree[pos][w];}return val[pos];}};
比较常用的也就是“字母树”和 树了吧, 数用得可能更经常些,字母树没什么好考的,要考也考 自动机(就是在 上跑 ),如果碰到 树的话,大多数都涉及到数字的位运算,这个比较考思维一些,但其实板子写得好,理解了板子,思路清晰的话,应该也写得比较快。
给定一个长度为 的字符串,问最少添加多少个字符,能够使得该字符串成为一个回文串。
这题比较考思维,想到了就很好写了,就是把原串反过来,然后求两个串的最长公共子序列长度,用总长度减去公共子序列长度就是答案。
还有注意一下这题卡空间,不能开 的 数组,用一下滚动数组就行了。
#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <climits>#include <cstring>#include <string>#include <vector>#include <list>#include <queue>#include <stack>#include <map>#include <set>#include <bitset>#include <algorithm>#include <functional>#include <iomanip>using namespace std;#define LL long longconst int maxn = 5000 + 100;int n;char str1[maxn], str2[maxn];int dp[2][maxn];int main() {#ifdef LOCALfreopen("test.txt", "r", stdin);// freopen("out.txt", "w", stdout);#endif // LOCALios::sync_with_stdio(false);while(scanf("%d", &n) != EOF) {scanf("%s", str1 + 1);int len = strlen(str1 + 1);for(int i = 1; i <= len; ++i) {str2[len - i + 1] = str1[i];}for(int i = 1; i <= len; ++i) {int ii = i % 2;int iii = (i + 1) % 2;for(int j = 1; j <= len; ++j) {if(str1[i] == str2[j]) {dp[ii][j] = max(max(dp[iii][j], dp[ii][j - 1]), dp[iii][j - 1] + 1);} else {dp[ii][j] = max(dp[iii][j], dp[ii][j - 1]);}}}printf("%d\n", len - dp[len % 2][len]);}return 0;}
裸题,就是这题说有 个字符串,但是每个字符串有 个字符,所以 的值应该为 (最大节点数,每一个节点为一个字母)。
#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <climits>#include <cstring>#include <string>#include <vector>#include <list>#include <queue>#include <stack>#include <map>#include <set>#include <bitset>#include <algorithm>#include <functional>#include <iomanip>using namespace std;#define LL long longconst int maxcnt = 1000000 + 100;const int Size = 30;struct Trie {int root, cnt;int tree[maxcnt][Size];int val[maxcnt];int create() {++cnt;memset(tree[cnt], 0, sizeof(tree[cnt]));val[cnt] = 0;return cnt;}void Init() {cnt = 0;root = create();}inline int id(const char &ch) {return ch - 'a';}void add(char *s) {int pos = root;for(int i = 1; s[i]; ++i) {int w = id(s[i]);if(tree[pos][w] == 0) {tree[pos][w] = create();}pos = tree[pos][w];++val[pos];}}int query(char *s) {int pos = root;for(int i = 1; s[i]; ++i) {int w = id(s[i]);if(tree[pos][w] == 0) {return 0;}pos = tree[pos][w];}return val[pos];}};int n, m;char str[15];Trie t;int main() {#ifdef LOCALfreopen("test.txt", "r", stdin);// freopen("out.txt", "w", stdout);#endif // LOCALios::sync_with_stdio(false);while(scanf("%d", &n) != EOF) {t.Init();for(int i = 0; i < n; ++i) {scanf("%s", str + 1);t.add(str);}scanf("%d", &m);for(int i = 0; i < m; ++i) {scanf("%s", str + 1);printf("%d\n", t.query(str));}}return 0;}
树,也算是裸题吧,就是把 个数字都放到 数中,然后对于每一次询问,从高位往低位贪心,如果存在某一位与 的相同位上的数字相反( 与 相反),就取那一位(这样那一位的异或值才为 ),如果不存在,则只能让那一位的异或值为 ,然后从高位到低位贪心。
#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <climits>#include <cstring>#include <string>#include <vector>#include <list>#include <queue>#include <stack>#include <map>#include <set>#include <bitset>#include <algorithm>#include <functional>#include <iomanip>using namespace std;#define LL long longconst int maxcnt = 3200000 + 100;const int Size = 2;struct Trie {int root, cnt;int tree[maxcnt][Size];int val[maxcnt];int create() {++cnt;memset(tree[cnt], 0, sizeof(tree[cnt]));return cnt;}void Init() {cnt = 0;root = create();}int id(int num, int dig) {return ((num >> (32 - dig)) & 1);}void add(int num) {int pos = root;for(int i = 1; i <= 32; ++i) {int w = id(num, i);if(tree[pos][w] == 0) {tree[pos][w] = create();}pos = tree[pos][w];}}int query(int num) {int ret = 0;int pos = root;for(int i = 1; i <= 32; ++i) {int w = !id(num, i);if(tree[pos][w] == 0) {w = !w;}ret |= (w << (32 - i));pos = tree[pos][w];}return ret;}};int T, n, m;int num;Trie t;int main() {#ifdef LOCALfreopen("test.txt", "r", stdin);// freopen("out.txt", "w", stdout);#endif // LOCALios::sync_with_stdio(false);scanf("%d", &T);for(int cas = 1; cas <= T; ++cas) {printf("Case #%d:\n", cas);t.Init();scanf("%d%d", &n, &m);for(int i = 0; i < n; ++i) {scanf("%d", &num);t.add(num);}for(int i = 0; i < m; ++i) {scanf("%d", &num);printf("%d\n", t.query(num));}}return 0;}