字符串模式匹配: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 要简单很多,加上扩展就显得太麻烦了。

原文地址:https://www.cnblogs.com/JMDWQ/p/2495548.html