【ZOJ3587】Marlon's String——白四爷×KMP 白濑肆の算法完全解读KMP篇 KMP来袭第二弹前缀什么的果然最讨厌了!【1.0%达成!】

5 篇文章 0 订阅
1 篇文章 0 订阅
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
/**
    Title: 【ZOJ3587】Marlon's String——白四爷×KMP 白濑肆の算法完全解读KMP篇 KMP来袭第二弹前缀什么的果然最讨厌了!【1.0%达成!】
    Problem: ZOJ3587 Marlon's String
    Knowledge Point : KMP算法の深入理解
    Pre-Knowledge: KMP算法的理解,可以看之前我发的
    http://blog.csdn.net/c0de4fun/article/details/7845625
    Thanks To: Lengxiang学长指出%I64d应换为%lld,欠你顿火锅~
    Reference:shllhs学长/学姐的一份代码,未找到原始出处,应该为该人所写,代码附后(见Rerfer Code)
    Thought:
    题意是说,主串中任意两处S[a..b] + S[c..d] = T即可,刚开始脑残把条件看成了a<b<c<d因此日了好久的后缀数组(看见前三个题两个被误认为后缀数组果然我是想多了帝)
    其实没有那么复杂,还有人用Extended-KMP做的,在网上搜索教程能看到的就是林希德的那篇文章,不过那篇文章主要介绍的是后缀树,鉴于罗大神的论文中说后缀数组的功能比
    后缀树的不逊色,我就懒得看了,本来人就懒。不过貌似后缀树是Trie树加上一些奇奇怪怪的东西(好像就跟AC自动机一样吧加了点其他的东西),用到的时候再说。
    言归正传,这道题的思路是这样的:
    我们将模式串和主串读入,求一遍KMP,求出模式串pattern的前缀们(【定义】:pattern[0..i] ( i <= 0 < strlen(pattern) )叫做串pattern的一个前缀)
    在主串中出现的位置。
    然后我们再把模式串和主串(下文pattern指模式串,text指子串,不再特殊说明)都翻转过来,求一遍翻转模式串reversePattern的前缀们在翻转主串reverseText中出现的次数。

    那么,ans就等于
    int ans = 0;
    for(int i = 0 ; i < lenPattern-1 ; i++)
    {
        ans += originOrder[i] * reverseOrder[len-1 - 1 - i];
    }
    这么说的原因是,我们假设原来模式串是abcdkkkk,翻转得到kkkkdcba
    index:    0 1 2 3 4 5 6 7  ( PatternLength = 8 )
    OriginS : a b c d k k k k
    ReverseS: k k k k d c b a

    那么,原顺序(OriginS)中,i = 2时候的Pattern的前缀pattern[0..2]出现的次数乘以
    reversePattern[0..5]出现的次数,才能得到主串中可以以这种方式拼出来模式串的次数。

    现在的问题是,前缀的出现次数不会求,怎么破?
    这就要用到KMP了好吧我承认我对KMP的理解太水了,以前只是知道快速匹配没想到还可以用来求前缀。
    这道题的亮点就在于求模式串的前缀,简直是逼着我们用KMP的说。

    屌丝们,还记得当初next数组的作用么。。。
    对了(我就是超威蓝猫?)~【next[i]的作用是确保pattern串中
    pattern[0..next[i]]与pattern[j-next[i],j]完全相等~】这句话注意,以后会用到。
    那么我们在kmp匹配的过程中,如果pattern[j] == text[i]的话,originValue[j](或者reverseValue[j],详见代码,取决于你是否将字符串翻转了)
    的值就要+1,因为这代表pattern[0..j]这个前缀在主串text中出现了。
    不过,如果只是这么求的话(亦即,遍历text一遍,求出所有前缀出现的次数)还会漏求一些前缀出现的次数。举个例子

    Pattern = abcdabcdabc
    对应的next为
    Pattern =  a  b  c  d  a  b  c  d  a  b  c
    next    = -1 -1 -1 -1  0  1  2  3  4  5  6
    但是我们注意到,如果在text中匹配到了abcdabc这个前缀,其实pattern的前缀abc也在主串中出现了,这怎么破!?
    递归破就可以了,递归是从尾巴向前的。
    for(int i = patternLen-1; i >= 0 ; i--)
    {
        if (next[i] != -1) //在前面出现了某前缀S[0..next[i]]与S[i-next[i]..i]相同
        originValue[next[i]] += originValue[i] ; (或者reverseValue[next[i]] += reverseValue[i],具体取决于你是否已经翻转主串和模式串了)
    }
    这就完爆了,这里如果KMP想不好的话,想起来有一些费劲,结合next[i]函数的意义想一下吧。

    如此求完了之后再乘起来就可以了呐。

*/
using namespace std;
const int ORIGIN_ORDER  = 1;
const int REVERSE_ORDER  = 0;
const int MAX_SIZE = 100005;
long long ans = 0;
char text[MAX_SIZE];
char pattern[MAX_SIZE];
int next[MAX_SIZE];
long long originValue[MAX_SIZE];
long long reverseValue[MAX_SIZE];
void init()
{
    memset(text,0,sizeof(text));
    memset(pattern,0,sizeof(pattern));
    memset(next,-1,sizeof(next));
    memset(originValue,0,sizeof(originValue));
    memset(reverseValue,0,sizeof(reverseValue));
}
void getNext()
{
    int len1 = strlen(pattern);
    next[0] = -1;
    for(int i = 1 ; i < len1  ; i++)
    {
        int j = next[i-1];
        while( pattern[j+1] != pattern[i] && j>=0 )
            j = next[j];
        if( pattern[j+1] == pattern[i] )
            next[i] = j+1;
        else
            next[i] = -1;
    }
}
bool KMP(int flag)
{
    getNext();
    int lenp = strlen(pattern);
    int lens = strlen(text);
    int i = 0 ;
    int j = 0 ;
    while( i < lens )
    {
        if( text[i] == pattern[j] )
        {
            if ( flag == ORIGIN_ORDER )
            {
                originValue[j]++;
            }
            if ( flag == REVERSE_ORDER )
            {
                reverseValue[j]++;
            }
            i++;
            j++;
        }
        else
        {
            if( j == 0 ) i++;
            else
            j = next[j-1]+1;
        }
    }
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("B:\\acm\\SummerVacation\\String-I\\C.in","r",stdin);
    freopen("B:\\acm\\SummerVacation\\String-I\\C.out","w",stdout);
#endif
    int T = 0;
    scanf("%d\n",&T);
    //while( scanf("%d\n",&T) != EOF )
    //{
        for(int t = 1 ; t <= T ; t++)
        {
            init();
            gets(text);
            gets(pattern);
            KMP(ORIGIN_ORDER);
            int lenp = strlen(pattern);
            int lent = strlen(text);
            for(int i = lenp-1 ; i >= 0 ; i-- )
            {
                if( next[i] > -1 )
                {
                    originValue[next[i]] = originValue[next[i]]+originValue[i];
                }
            }
            /**以下内容全是用来翻转字符串的*/
            char tmp;
            for(int i = 0 ; i < lenp/2; i++)
            {
                tmp = pattern[i];
                pattern[i] = pattern[lenp-1-i];
                pattern[lenp-1-i] = tmp;
            }
            for(int i = 0 ; i < lent/2; i++)
            {
                tmp = text[i];
                text[i] = text[lent-1-i];
                text[lent-1-i]=tmp;
            }
            memset(next,-1,sizeof(next));
            /**翻转+初始化NEXT数组完毕**/

            KMP(REVERSE_ORDER);

            for(int i = lenp-1 ; i >= 0 ; i--)
            {
                if( next[i] > -1 )
                    reverseValue[next[i]] = reverseValue[next[i]]+reverseValue[i];
            }

            ans = 0;


            for(int i = 0 ; i < lenp-1 ; i++)
            {
                ans = ans + originValue[i]*reverseValue[lenp-1-i-1];
            }
            printf("%lld\n",ans);

        }
    //}
#ifndef ONLINE_JUDGE
    fclose(stdin);
    fclose(stdout);
#endif
    return 0;
}

参考代码:(Thx to shllhs)


#include <cstring>
#include <cstdio>
#include <algorithm>
#define MAXN 100010
using namespace std;
int next[MAXN];
char str[MAXN],s[MAXN];
long long a[MAXN],b[MAXN];
void KMP_next(char *s,int *next){
    int i,j;
    j=next[0]=-1;
    for(i=1;s[i]!='\0';i++)
    {
        next[i]=-1;
        while(j>-1&&s[j+1]!=s[i])
            j=next[j];
        if(s[j+1]==s[i])
        {
            j++;
            next[i]=j;
        }
    }
}
int main(){
    int i,j,t,n,m;
    long long ans;
    scanf("%d",&t);
    while(t--){
        scanf("%s%s",str,s);
        KMP_next(s,next);
        m=strlen(str);
        n=strlen(s);
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        ans=0;
        for(i=0,j=-1;i<m;i++){
            while(j>-1&&s[j+1]!=str[i])
                j=next[j];
            if(s[j+1]==str[i]){
                j++;
                a[j]++;
                               }
                             }
        for(i=n-1;i>0;i--)
            if(next[i]>-1)
                a[next[i]]+=a[i];
        for(i=0;i+i<m;i++)
            swap(str[i],str[m-1-i]);
        for(i=0;i+i<n;i++)
            swap(s[i],s[n-1-i]);
        KMP_next(s,next);        
        for(i=0,j=-1;i<m;i++){
            while(j>-1&&s[j+1]!=str[i])
                j=next[j];
            if(s[j+1]==str[i]){
                j++;
                b[j]++;
                               }
                             }
        for(i=n-1;i>0;i--)
            if(next[i]>-1)
                b[next[i]]+=b[i];
        for(i=0;i+1<n;i++)
            ans+=a[i]*b[n-2-i];
        printf("%lld\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值