序列自动机入门
文章发布于摸鱼世界,转载请注明出处。
前言
叫什么自动姬嘛,一个数组的事情(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]);
}
(记忆化搜索可以优化时间)
基础操作就讲到这里,如果以后遇到有趣的题也不来更新就来更新。
你听到鸽子叫了吗~