接触序列自动机是因为训练的时候遇到的两个题目
牛客:https://ac.nowcoder.com/acm/contest/392/J
计蒜客:https://nanti.jisuanke.com/t/38232
序列自动机是在O(n)的复杂度内判断一个字符串是否是另一个字符串的子串(定义请自行百度)
先上代码(懂原理的可以到此为止了)
for(int i=n; i>0; i--)
{
for(int j=1; j<=26; j++) nex[i-1][j-'a'] = nex[i][j-'a']; //26是假定字符集为小写字母
nex[i-1][s[i]-'a'] = i;
}
序列自动机就是开一个辅助空间nex[MAX_N][MAX_字符集],nex[i][j]表示的是在位置 i 的后面第一次出现 j 时的位置(贪心出现),在匹配子串时即可不断寻找子串的next元素最先在主串中出现的位置
我们就可以以递推的方式来存值了,多说无益,模拟才是王道
主串:abcda ,主串长度n = 5,下标从1的5,0的位置是虚设的,字符集 S = {a,b,c,d}
nex[i][a] | nex[i][b] | nex[i][c] | nex[i][d] | |
---|---|---|---|---|
0 | 1 | 2 | 3 | 4 |
1 | 5 | 2 | 3 | 4 |
2 | 5 | 0 | 3 | 4 |
3 | 5 | 0 | 0 | 4 |
4 | 5 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 |
表格倒着看才对,在i==5的位置全0是初始的边界条件,接下来某个值为0就是在 i 位置后面没有 j 字符出现了
剩下的就是一个贪心的匹配过程,照着表格手动模拟一发即可
ps: nex[0][j]是很有用的,可以告诉我们元素j第一次出现在主串中的位置,所以输入的时候我们从s+1的位置开始输入,s[0]虚设成一个字符‘&’,不然的strlen(s)会返回0啊
串的匹配,都在代码里
char c; //读入子串
int f=0,p=0; //p初始化为0,nex[0][c]表示c第一次出现的位置
while((c = getchar()) != '\n')
if((p = nex[p][c-'a']) == 0) f = 1; //递推找位置,p后面没有c就不能匹配成子串了
if(f) printf("NO\n");
else printf("YES\n");
我们假设主串长度lens,子串长度lenb,有n个子串,那么这个复杂度就是O(lens+n*lenb)
#include<stdio.h>
#include<string.h>
using namespace std;
typedef long long LL;
const int MAX_N = 1e5+50;
char s[MAX_N],c;
int nex[MAX_N][28],n,p,q;
int main(void)
{
s[0] = '&'; //虚设一个用不到的字符
scanf("%s",s+1);
int len = strlen(s);
for(int i=len; i>0; i--)
{
for(int j='a'; j<='z'; j++) nex[i-1][j-'a'] = nex[i][j-'a'];
nex[i-1][s[i]-'a'] = i;
}
scanf("%d",&n);
getchar();
while(n--)
{
int f = 0,p=0;
while((c = getchar()) != '\n') //子串不需要存啊
if((p = nex[p][c-'a']) == 0) f = 1;
if(f) printf("NO\n"); //按需要修改咯
else printf("YES\n");
}
return 0;
}
结束求波关注