字符串模式匹配:POJ 3461 Oulipo
这道题是字符串的模式匹配,要求计算出模式串在文本串中出现的次数,比如:"AZA" 在 "AZAZAZA" 中出现了 3 次;
这道题使用 KMP 过的,但是 horspool 却不能过,尝试了一下各种方法后,还是回到了麻烦的 KMP,留下了以下几个模版:
1. shift-or,据说比 KMP 快两倍,但只适用于模式串在 32 位以内,64位以内的也可以,需要把 b[] 改为 long long int 型,超出范围的可以尝试自己构造长类型……这道题不适用
# include <string.h> int b[128]; /* int型共32位,模式串不超过32位 */ char p[32], t[1000005]; int shift_or(char *p, char *t) { int n, m, i, d, f, ret; ret = 0; n = strlen(p); m = strlen(t); memset(b, 0xff, sizeof(b)); for (i = 0; i < n; ++i) b[p[i]] &= ~(0x1<<i); d = 0xff; f = 0x1 << (n-1); for (i = 0; i < m; ++i) { d = (d<<1) | b[t[i]]; if (!(d&f)) ++ret; } return ret; }
2. horspool,基于后缀搜索,据说比 KMP 快,但是这道题 TLE 了
# include <stdio.h> # include <string.h> int d[128]; char p[10005], t[1000005]; int horspool(char *p, char *t); int main() { int T; p[0] = t[0] = '*'; /* 数组下标改为从1开始,方便不少 */ scanf("%d", &T); while (T--) { scanf("%s%s", p+1, t+1); printf("%d\n", horspool(p, t)); } return 0; } int horspool(char *p, char *t) { int m, n, i, ret, ptr; ret = 0; m = strlen(p) - 1; n = strlen(t) - 1; for (i = 'A'; i <= 'Z'; ++i) d[i] = m; /* i 循环是对整个字母表 */ for (i = 1; i <= m-1; ++i) d[p[i]] = m - i; ptr = 0; while (ptr <= n - m) { i = m; while (i > 0 && t[ptr+i] == p[i]) --i; if (i == 0) ++ret; ptr = ptr + d[t[ptr+m]]; } return ret; }
3. 最省事的 strstr, 当然会 TLE 了
# include <string.h> char p[10005], t[1000005]; int strstr_mat(char *p, char *t) { int ret; char *ptr; ret = 0; while (ptr = strstr(t, p)) { ++ret; t += ptr-t+1; } return ret; }
4. 最后是 KMP,AC用的这个,80MS,这个算法我总是理解着很困难,且理解后很快就忘了……,对照书上的伪代码比较好写
# include <stdio.h> # include <string.h> short pi[10005]; /* 这道题 short 够用 */ char p[10005], t[1000005]; int kmp(char *p, char *t); void comp_prefix(char *p, int plen); int main() { int T; p[0] = t[0] = '*'; /* 同样是为了方便,将下标改为从1开始 */ scanf("%d", &T); while (T--) { scanf("%s%s", p+1, t+1); printf("%d\n", kmp(p, t)); } return 0; } int kmp(char *p, char *t) { int n, m, i, q, ret; ret = 0; m = strlen(p) - 1; n = strlen(t) - 1; comp_prefix(p, m); q = 0; for (i = 1; i <= n; ++i) { while (q > 0 && p[q+1] != t[i]) q = pi[q]; if (p[q+1] == t[i]) ++q; if (q == m) { ++ret; q = pi[q]; } } return ret; } void comp_prefix(char *p, int plen) { int q, k; pi[1] = 0; k = 0; for (q = 2; q <= plen; ++q) { while (k > 0 && p[k+1] != p[q]) k = pi[k]; if (p[k+1] == p[q]) ++k; pi[q] = k; } }
shift-or 的扩展或许更有效,没有试,本来比 kmp 要简单很多,加上扩展就显得太麻烦了。