【回味】奥妙重重的字符串算法

题记

蒟蒻发现自己的字符串算法基本上要丢完了……还是要捡一捡……

Round1 R o u n d 1 - KMP K M P

所以说作为一个字符串匹配算法, KMP K M P 还是很重要的。尤其是 KMP K M P 的神奇 next n e x t 数组,可以解决循环串问题和一般的字符串匹配问题……
具体步骤:
1. 求出模式串的 next n e x t 数组
2. 匹配
所以敲一个代码试试吧……
next n e x t 数组的代码

void getnext(char *A,int *nex,int n)
{
    memset(nex,0,sizeof(nex));
    nex[0]=-1;
    int i=0,j=-1;
    while(i<n)
    {
        if(j==-1||A[i]==A[j])
            nex[++i]=++j;
        else
            j=nex[j];
    }
}

匹配代码最简单了~

int KMP(char *A,char *B,int *nex,int n,int m)
{
    int i=0,j=-1;
    while(i<m&&j<n)
    {
        if(j==-1||B[i]==A[j])
            i++,j++;
        else
            j=nex[j];
    }
    if(j==n)
        return 1;
    return 0;
}

可以发现求 next n e x t 数组的过程其实与匹配的过程十分相像,只不过变成了模式串自我匹配而已。

Round2 R o u n d 2 - exKMP e x K M P

曾经 too naive t o o   n a i v e 的我(虽然现在也很 naive n a i v e )一度写下过这样的话

貌似KMP可以解决的问题exKMP都能解决,而exKMP能解决的问题KMP不一定能解决

然后在一道循环串题目中被无情打脸,因为它要求的是每一个前缀是否是循环串,然后要输出最大循环节长度,用 exKMP e x K M P 做很困难……(如果错了,望轻喷)

不过 exKMP e x K M P 确实是个不错的算法,快要忘完的我再次回忆一下写法
求出 next n e x t

void getnext(char *A,int *nex,int n)
{
    memset(nex,0,sizeof(nex));
    nex[0]=n;
    for(int i=1;i<n;i++)
        if(A[i]!=A[i-1])
            break;
        else
            nex[1]++;
    int po=1,maxp=nex[1]+1;
    for(int i=2;i<n;i++)
    {
        if(i+nex[i-po]<maxp)nex[i]=nex[i-po];
        else nex[i]=max(maxp-i,0)
        while(i+nex[i]<n&&A[nex[i]]==A[i+nex[i]])nex[i]++;
        if(i+nex[i]>maxp)maxp=i+nex[i],po=i;
    }
}

然后是匹配 B B 串的时候,很类似的啦……

void exKMP(char *A,char *B,int *nex,int *ex,int n,int m)
{
    for(int 0=1;i<n&&i<m;i++)
        if(A[i]!=B[i])
            break;
        else
            ex[0]++;
    int po=0,maxp=ex[0];
    for(int i=1;i<m;i++)
    {
        if(i+nex[i-po]<maxp)ex[i]=nex[i-po];
        else ex[i]=max(maxp-i,0)
        while(ex[i]<n&&i+ex[i]<m&&A[ex[i]]==B[i+ex[i]])ex[i]++;
        if(i+ex[i]>maxp)maxp=i+ex[i],po=i;
    }
}

Round3- manacher m a n a c h e r

记得最清楚的一个算法……由于奇串和偶串的特判让人心烦,所以我们添加奇怪的字符(不出现在原串中,一般是# @ &等)在两个相邻字符中,然后在头尾再增加另外的字符避免查找时溢出的特判
然后,我们记录一下 po p o maxp(po+len[po]) m a x p ( p o + l e n [ p o ] ) (是不是似曾相识?),表示某一个以 po p o 为中心的回文串右端点为 maxp m a x p (必须是最靠右的),找到 i i 关于 po 的对称点 2poi 2 ∗ p o − i ,如果 len[2poi]<maxpi l e n [ 2 ∗ p o − i ] < m a x p − i ,根据回文的对称可得 len[i]=len[2poi] l e n [ i ] = l e n [ 2 ∗ p o − i ] ,否则就不确定了,但有一点可以确定, len[i]>=maxpi l e n [ i ] >= m a x p − i ,剩下的部分枚举就好了,由于一旦被枚举过,就会被 maxp m a x p 覆盖,因此复杂度还是 O(n) O ( n )

int manachar(char *A,int *len)//注意这里的A已经预处理过了
{
    for(int i=1;i<=2*n+1;i++)
    {
        if(i<mx)len[i]=min(mx-i,len[2*po-i]);
        else len[i]=1;
        while(A[i+len[i]]==A[i-len[i]])len[i]++;
        if(len[i]+i>mx)
            mx=len[i]+i,po=i;
        ans=max(ans,len[i]);
    }
}

Round4 R o u n d 4 - AC A C 自动机

KMP K M P 的升级版,但仍然很快,把所有的模式串建成一颗 Trie T r i e 树,再加上 fail f a i l 指针(与 next n e x t 数组类似),就可以实现多模式串匹配,但是要注意的是,需要在匹配成功后将 fail f a i l 链上所有的点都打上标记(不然不就和只在一个串上匹配一样了吗……),然后有一个很神的优化,把空指针连到 fail f a i l 的对应位置( fail f a i l
话不多说上代码(以小写字母为例)

struct Trie
{
    int cnt,nex[30],fail;
}A[N];
void insert(char *S,int w)
{
    int len=strlen(S),val,r=root;
    for(int i=0;i<len;i++)
    {
        val=S[i]-'a';
        if(!A[r].nex[val])
            A[r].nex[val]=++tot;
        r=A[r].nex[val];
    }
    A[r].cnt=w;
}
void build()
{
    int r=root;
    A[r].fail=r;
    q[tail++]=r;
    while(head<tail)
    {
        r=q[head++];
        for(int i=0;i<26;i++)
        {
            int ch,p;
            if((ch=A[r].nex[i]))
            {
                q[tail++]=ch;
                for(p=A[r].fail;p!=root&&!A[p].nex[i];p=A[p].fail);
                int tmp=A[p].nex[i];
                if(tmp&&tmp!=ch)A[ch].fail=tmp;
                else A[ch].fail=root;
            }
            else
            {
                for(p=A[r].fail;p!=root&&!A[p].nex[i];p=A[p].fail);
                int tmp=A[p].nex[i];
                if(tmp)A[r].nex[i]=tmp;
                else A[r].nex[i]=root;
            }
        }
    }
}
void query(char *S,int w)
{
    memset(vis,0,sizeof(vis));
    int sum=0,len=strlen(S),val,r=root;
    for(int i=0;i<len;i++)
    {
        val=S[i]-'a';
        r=A[r].nex[val];
        for(int p=r;p!=root&&vis[p]!=-1;p=A[p].fail)
        {
            vis[p]=-1;
            if(A[p].cnt)
                vir[++sum]=A[p].cnt;
        }
    }
    printf("%d\n",sum);
}

Round5 R o u n d 5 -后缀数组

啊啊啊啊啊——最不熟的就是这玩意了……
后缀数组的代码只会写 O(nlogn) O ( n log ⁡ n ) 的倍增,具体步骤呢就是基数排序啦……
然后还有一个 height h e i g h t 数组,非常常用,所以这个模板还是要背下来才好啊……
详情请见代码

int sa[N],rk[N],ht[N],wa[N],wb[N],ws[N],wv[N],n,m;
//m是长度n与字符集大小的最大值
char S[N];
//下标从1开始
void Da()
{
    int *x=wa,*y=wb,*t;
    for(int i=0;i<=m;i++)ws[i]=0;
    for(int i=1;i<=n;i++)ws[x[i]=S[i]]++;
    for(int i=1;i<=m;i++)ws[i]+=w[i-1];
    for(int i=1;i<=n;i++)sa[ws[x[i]]--]=i;
    for(int j=1,p=1;p<n;j<<=1)
    {
        p=0;
        for(int i=n-j+1;i<=n;i++)y[++p]=i;
        for(int i=1;i<=n;i++)if(sa[i]>j)y[++p]=sa[i]-j;
        for(int i=1;i<=n;i++)wv[i]=x[y[i]];
        for(int i=0;i<=m;i++)ws[i]=0;
        for(int i=1;i<=n;i++)ws[wv[i]]++;
        for(int i=1;i<=m;i++)ws[i]+=ws[i-1];
        for(int i=1;i<=n;i++)sa[ws[wv[i]]--]=y[i];
        t=x;x=y;y=t;
        x[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j])x[sa[i]]=p;
            else x[sa[i]]=++p;
    }
}
void Ca()
{
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    int k=0;
    for(int i=2;i<=n;i++)
    {
        if(k)k--;
        for(int j=sa[rk[i]-1];S[i+k]==S[j+k];k++)
        ht[rk[i]]=k;
    }
}

Round6 R o u n d 6 -后缀自动机

同样非常的迷……看了很多博客才有一点点懂了……
首先,将后缀自动机构造出来,其中有一个指针 link l i n k ,可以构成一棵树,这棵树的每一个叶子都恰好对应了一个串的后缀,有很多的恶心性质,可以解决很多 magical m a g i c a l 的问题,但是并不会证明这些性质。 btw b t w ,貌似只有我认为后缀自动机比后缀数组好写?(也可能是我太弱了,然而还是基本靠背才会写)

void insert(int c)
{
    int p,cur=++cnt;
    A[cur].len=A[last].len+1;
    for(p=last;p!=-1&&!A[p].nex[c];p=A[p].link)
        A[p].nex[c]=cur;
    if(p<0)
        A[cur].link=0;
    else
    {
        int q=A[p].nex[c];
        if(A[q].len==A[p].len+1)
            A[cur].link=q;
        else
        {
            int clone=++cnt;
            A[clone]=A[q];
            A[clone].len=A[p].len+1;
            for(;p!=-1&&A[p].nex[c]==q;p=A[p].link)
                A[p].nex[c]=clone;
            A[q].link=A[cur].link=clone;
        }
    }
    last=cur;
}

The end T h e   e n d

好了就到这里了,不够完整的话以后再填坑(加入有生之年系列)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值