序列自动机入门

文章发布于摸鱼世界,转载请注明出处。


前言

叫什么自动姬嘛,一个数组的事情(bushi


正文

吾日三省吾身:

这玩意有啥用?

这玩意咋做的?

这玩意咋写?

先来看序列自动机是干什么用的。

看它的名字就知道这和子序列有关,没错,它的基本作用就是快速的查询一个串是否是母串的子序列/母串的子序列个数等问题。当然,拓展的问题我也不会。


引入一个二维数组\(nxt[][]\)\(nxt[i][j]\)表示第\(i\)位之后(不含第\(i\)位)的第一个字符\(j\)的位置。字符是不能直接作为数组的下标的,所以我们就把它们的\(ACSll\)码减去其中值最小的字符的值作为下标。比如字符都是'a'-'z',我们就可以把字符减去'a'来作为下标,节省空间。

但是需要注意的一个细节是要把第0个位置空出来,这是状态的起点。所以输入的时候就用scanf("%s",str+1);即可。

如果有这样一个数组,我们就可以把每次要判断的串放到自动机里跑,直到全部匹配或是匹配失败。

那么重点就是如何实现这个nxt数组的维护。

首先有两个明显的结论。

1.如果第i位的字符为j,那么有nxt[i-1][j]=i

2.除了字符j以外的字符集内任意一个字符k,都有nxt[i-1][k]=nxt[i][k]

我们如果要用这两条规律进行推导的话,因为在2中是要用nxt[i][k]推出nxt[i-1][k],所以循环的时候我们通过逆循环来实现推导就好了。

下面是这一步的代码块:(字符集'a'-'z')

void getnext(char str[])
{
    int len=strlen(str+1);
    for(int i=len;i>=1;i--)
    {
        for(int j=0;j<26;j++)
            c[i-1][j]=c[i][j];
        c[i-1][str[i]-'a']=i;
    }
    return;
}

接下来还是要说一下查询(一个串是否是母串的子序列)应该如何实现。

我们只需要像其他自动机(指AC自动机)那样从初始状态(i=0)开始往下循环,每次如果可行,就跳到nxt[i][str[i]-'a'],如果不可行,那么就是不匹配,直接退出循环就好。

void ask(char str[])
{
    bool s=1;
    int u=0;
    for(int i=0;str[i];i++)
    {
        u=c[u][str[i]-'a'];
        if(!u)
        {
            s=0;
            break;
        }
    }
    puts(s?"Yes":"No");
}

最后一个基本操作,求(母串的(不重复的子序列)的个数)的实现。

把这个自动机看做是一棵树,每条边即为从i出发,连向所有nxt[i][j]。而不重复子序列的个数,只需要统计一下这棵树上的结点个数即可。

至于为什么?画个图感性理解一下吧我也不会证 ::aru:diving::

int cnt=0;
void dfs(int u)
{
    cnt++;
    for(int i=0;i<26;i++)
        if(c[u][i])dfs(c[u][i]);
}

(记忆化搜索可以优化时间)


基础操作就讲到这里,如果以后遇到有趣的题也不来更新就来更新。

你听到鸽子叫了吗~

posted @ 2020-04-13 22:56  摸鱼酱  阅读(299)  评论(0编辑  收藏  举报