kuangbin专题十六KMP & 扩展KMP & Manacher总结

本专题的要求是熟练掌握这三个算法。能够灵活运用它们,并且可以修改它们。
K - Clairewd’s message
一开始理解错题意了,以为给的串中有多个暗码和明码,于是一点头绪都没有,看了题解之后才知道只有一个暗码和明码,那么做法就很显然了,先把串通过给的映射变换一下,那么它就变成明码+乱七八糟的东西了,然后再拓展kmp就行了。

#include<bits/stdc++.h>
using namespace std;
char tab[30];
const int maxn=100005;
char s[maxn],_s[maxn];
int Next[maxn],extend[maxn];
void preEkmp(char s[],int slen)
{
    Next[0]=slen;
    int j=0;
    while(j+1<slen&&s[j]==s[j+1])j++;
    Next[1]=j;
    int k=1;
    for(int i=2;i<slen;i++)
    {
        int p=k+Next[k]-1;
        int L=Next[i-k];
        if(i+L-1<p)Next[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(i+j<slen&&s[j]==s[j+i])j++;
            Next[i]=j;
            k=i;
        }
    }
}

void Ekmp(char _s[],int _slen,char s[],int slen)
{
    preEkmp(_s,_slen);
    int j=0;
    while(j<_slen&&_s[j]==s[j])j++;
    extend[0]=j;
    int k=0;
    for(int i=1;i<slen;i++)
    {
        int p=k+extend[k]-1;
        int L=Next[i-k];
        if(i+L-1<p)extend[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(i+j<slen&&_s[j]==s[i+j])j++;
            extend[i]=j;
            k=i;
        }
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        char tmp[30];
        scanf("%s",tmp);
        for(int i=0;i<26;i++)
            tab[tmp[i]-'a']=i+'a';
        scanf("%s",s);
        int slen=strlen(s);
        memset(_s,'\0',sizeof(_s));
        for(int i=0;i<slen;i++)
            _s[i]=tab[s[i]-'a'];
        Ekmp(_s,slen,s,slen);
        bool flag=true;
        for(int i=(slen+1)/2;i<slen;i++)
        {
            if(extend[i]+i==slen)
                {
                    flag=false;
                    printf("%s",s);
                    for(int j=extend[i];j<i;j++)
                        printf("%c",_s[j]);
                    break;
                }
        }
        if(flag)
        {
            printf("%s%s",s,_s);
        }
        puts("");
    }

    return 0;
}

N - String Problem
这题关键在于如何求出最小和最大的串,这里就有一个小知识点,字符串的最小表示法。该算法可以通过O(n)计算出最小的串。我会在另一篇博客详细讲解该算法。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
char s[maxn*2];

int Findmin(char s[],int len)
{
    int i=0,j=1;
    int k=0;
    int t;
    while(i<len&&j<len&&k<len)
    {
        t=s[(i+k)%len]-s[(j+k)%len];
        if(!t)k++;
        else
        {
            if(t>0)i+=k+1;
            else j+=k+1;
            if(i==j)j++;
            k=0;
        }
    }
    return min(i,j);
}
int Findmax(char s[],int len)
{
    int i=0,j=1;
    int k=0;
    int t;
    while(i<len&&j<len&&k<len)
    {
        t=s[(i+k)%len]-s[(j+k)%len];
        if(!t)k++;
        else
        {
            if(t<0)i+=k+1;
            else j+=k+1;
            if(i==j)j++;
            k=0;
        }
    }
    return min(i,j);
}
char tmp[maxn];
int Next[maxn];
void preKmp(char a[],int alen)
{
    Next[0]=-1;
    int i=0,j=-1;
    while(i<alen)
    {
        while(j!=-1&&a[i]!=a[j])j=Next[j];
        Next[++i]=++j;
    }
}


int Kmp(char a[],int alen,char b[],int blen)
{
    preKmp(a,alen);
    int i=0,j=0;
    int ans=0;
    while(i<blen-1)
    {
        while(j!=-1&&b[i]!=a[j])j=Next[j];
        ++i,++j;
        if(j>=alen)
        {
            ans++;
        }
    }
    return ans;
}

int main()
{
    while(~scanf("%s",s))
    {
        int slen=strlen(s);
        int miidx=Findmin(s,slen);
        int maidx=Findmax(s,slen);
        memset(tmp,'\0',sizeof(tmp));
        for(int i=0;i<slen;i++)
            tmp[i]=s[(miidx+i)%slen];
        for(int i=0;i<slen;i++)
            s[i+slen]=s[i];
        s[slen*2]='\0';
        printf("%d %d ",miidx+1,Kmp(tmp,slen,s,2*slen));
        for(int i=0;i<slen;i++)
            tmp[i]=s[(maidx+i)%slen];
        printf("%d %d\n",maidx+1,Kmp(tmp,slen,s,2*slen));
    }
    return 0;
}

S - Finding Palindromes
本专题最好的一道题。
思路是不可能有的,只能看题解,才能做出来这样子。
如何判断一个串和另一个串连起来是否是回文串呢?这里有一个技巧。
设现在有串a和b,令len(a)< len(b),如果a+b是一个回文串的话,那么a的前缀必然等于b的反串的前缀,而b剩下的子串也必然是回文串。
如果b+a是回文串的话,那么b的前缀必然等于a的后缀,b剩下的子串必然是回文串。
我们现在来建立一颗字典树,将所有串的反串放进去,然后拿串a和字典树去匹配。如果a已经匹配完了,但字典树中a下面还有节点,那么我们需要知道a下面的节点所组成的串是不是回文串(第一种情况)。
如果a匹配到一个节点是一个串的终止位置,那么我们需要知道a后面的节点是否是回文串(第二种情况)。
所以现在的问题是如何知道一个串的后缀是否是回文串,这里用到了拓展kmp,只需要将串和他的反串进行拓展kmp,然后判断一下就知道后缀是否是回文串了。
根据上述讲解,字典树需要维护该节点是否是串的终止位置,和该节点后面有多少个回文串。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
const int maxn=2000005;
char s[maxn];
int Begin[maxn];
char tmp[maxn],tmp2[maxn];
int Next[maxn];
int extend[maxn];
void preEkmp(char a[],int len)
{
    Next[0]=len;
    int j=0;
    while(j+1<len&&a[j]==a[j+1])j++;
    Next[1]=j;
    int k=1;
    for(int i=2; i<len; i++)
    {
        int p=k+Next[k]-1;
        int L=Next[i-k];
        if(i+L-1<p)Next[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(i+j<len&&a[j]==a[j+i])j++;
            Next[i]=j;
            k=i;
        }
    }
}

void Ekmp(char a[],int alen,char b[],int blen)
{
    preEkmp(a,alen);
    int j=0;
    while(j<alen&&j<blen&&a[j]==b[j])j++;
    Next[0]=j;
    int k=0;
    for(int i=1; i<blen; i++)
    {
        int p=k+extend[k]-1;
        int L=Next[i-k];
        if(i+L-1<p)extend[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(j+i<blen&&j<alen&&a[j]==b[j+i])j++;
            extend[i]=j;
            k=i;
        }
    }

}
int cnt,root;
int nxt[maxn][27];
int End[maxn],val[maxn];

int Newnode()
{
    cnt++;
    memset(nxt[cnt],-1,sizeof(nxt[cnt]));
    End[cnt]=val[cnt]=0;
    return cnt;
}

void Insert(char s[],int len)
{
    int now=root;
    for(int i=0; i<len; i++)
    {
        int go=s[i]-'a';
        if(nxt[now][go]==-1)
            nxt[now][go]=Newnode();
        if(extend[i]+i==len)
            val[now]++;
        now=nxt[now][go];
    }

    End[now]++;
}

int query(char s[],int len)
{
    int now=root;
    ll res=0;
    int i;
    for(i=0; i<len; i++)
    {
        int go=s[i]-'a';
        int id=nxt[now][go];
        if(id==-1)break;
        if(End[id]!=0)
        {
            if(i+1==len||extend[i+1]+i+1==len)
                res+=End[id];
        }
        now=id;
    }
    if(i==len)
        res+=val[now];
    return res;
}

int main()
{
    int n;
    scanf("%d",&n);
    int now=0;
    int len;
    for(int i=1; i<=n; i++)
    {
        scanf("%d %s",&len,s+now);
        Begin[i]=now;
        now+=len;
    }
    Begin[n+1]=now;
    root=Newnode();
    for(int i=1; i<=n; i++)
    {
        now=0;
        for(int j=Begin[i+1]-1; j>=Begin[i]; j--)
            tmp[now++]=s[j];
        tmp[now]='\0';
        now=0;
        for(int j=Begin[i];j<Begin[i+1]; j++)
            tmp2[now++]=s[j];
        tmp2[now]='\0';
        len=Begin[i+1]-Begin[i];
        Ekmp(tmp2,len,tmp,len);
        Insert(tmp,len);
    }
    ll ans=0;
    for(int i=1; i<=n; i++)
    {
        now=0;
        for(int j=Begin[i+1]-1; j>=Begin[i]; j--)
            tmp[now++]=s[j];
        tmp[now]='\0';
        now=0;
        for(int j=Begin[i]; j<Begin[i+1]; j++)
            tmp2[now++]=s[j];
        tmp2[now]='\0';
        len=Begin[i+1]-Begin[i];
        Ekmp(tmp,len,tmp2,len);
        ans+=query(tmp2,Begin[i+1]-Begin[i]);
    }
    cout<<ans<<endl;

}

本来用结构体来存储的,结果mle了。。改成直接数组就过了。学到了一个减少内存的方法了。
Y - Theme Section
求一个形如EAEAE的串E的长度,拓展kmp后枚举判断一下即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
char str[maxn];
int Next[maxn];
void preEkmp(char s[],int len)
{
    Next[0]=len;
    int j=0;
    while(j+1<len&&s[j]==s[j+1])j++;
    Next[1]=j;
    int k=1;
    for(int i=2;i<len;i++)
    {
        int p=k+Next[k]-1;
        int L=Next[i-k];
        if(i+L-1<p)Next[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(j+i<len&&s[j]==s[j+i])j++;
            Next[i]=j;
            k=i;
        }
    }
}

bool check(int idx)
{
    int len=strlen(str);
    len=len-idx;
    for(int i=len;i+len-1<idx;i++)
        if(Next[i]>=len)return 1;
    return 0;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",str);
        int len=strlen(str);
        preEkmp(str,len);
        int first=len/3;
        first=len-first;
        int ans=0;
        for(int i=first;i<len;i++)
        {
            if(Next[i]+i==len&&check(i))
            {
                ans=Next[i];
                break;
            }
        }
        cout<<ans<<endl;
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值