序列自动机
算法分析
序列自动机实质还是用空间换时间,它有一个数组 ne[i][j]表示原串s的第i位后面那26个字符j出现的最早的 位置。
相当于建一棵树,根节点是一个空节点,它有26个孩子,表示每一个字母最早出现的位置
举个例子:
s = “abcca” (默认数组下标为1-5) (a–z==0–25) ne[0][0] = 1。即字符串中出现的第一个字符’a’的位置为1。
ne[1][1] = 2,即字符串距离下标1之后的字符’b’出现的第一个位置为2
以此类推我们可以得到 ne[1][0] = 5(距离字符串下标1的首a位置),ne[1][2] = 3 (距离字符串下标1的首c位置)等等
同样第一个字符也有这样的26个孩子,这样从根节点到任意一个叶子节点都是原串的一个子序列,这样判断一个字符串t是不是原串的子序列只要将t中的每一个字符在那棵树里跑一下,如果存在这样的路径就表示t是s的一个子序列。
那么怎么建树呢?
如果正着建树的话每次都要找到后面最早出现的字符的位置,操作过于复杂,所以我们倒着建树,用一个 tree[30]数组表示遍历到第i个字符时后面这26个字符从后往前看最晚出现的位置,也就是第i个字符后面的26个字符最在出现的位置,用它来更新 然后再将这个字符在 tree数组中的位置更新为当前的位置tree[s[i]−′a′]=i
构建序列自动机
ne[i][j]存的是在字符串 s 中第 i 位后面第一个 字母出现的位置。(0<j<26)
void build()
{
memset(tree,-1,sizeof(tree));//初始化未出现的字符
//处理每一个字符
for(int i=s.length();i>=0;i--)
{
//找出第i个字符后面的26个字母最早出现的字符的位置
for(int j=0;j<26;j++)
ne[i][j]=tree[j];
//用当前字符更新当前字符在原串中从后向前最晚出现的位置
tree[s[i]-'a']=i;
}
}
子序列查询
查询字符串 t 是否是上述 s 的子序列
bool check()
{
int start=tree[t[0]-'a'];//start为t第一个字符位置
if(start==-1)
return false;//第一个字符不在s中
for(int i=1;i<t.length();i++)
{
start=ne[start][t[i]-'a'];//位置移动的下一个字符的位置
if(start==-1)
return false;
}
return true;
}
模板题:月月查华华的手机
输入
noiauwfaurainairtqltqlmomomo
8
rain
air
tql
ntt
xiaobai
oiiiooo
orzcnzcnznb
ooooo
输出
Yes
Yes
Yes
Yes
No
Yes
No
No
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7;
string s,t;
int ne[N][30];
int tree[30];//在字符串最前面出现的位置
void build()
{ memset(tree,-1,sizeof(tree));//初始化未出现的字符
//处理每一个字符
for(int i=s.length();i>=0;i--)
{
//找出第i个字符后面的26个字母最早出现的字符的位置
for(int j=0;j<26;j++)
ne[i][j]=tree[j];
//用当前字符更新当前字符在原串中从后向前最晚出现的位置
tree[s[i]-'a']=i;
}
}
bool check()
{
int start=tree[t[0]-'a'];//start为t第一个字符位置
if(start==-1)
return false;//第一个字符不在s中
for(int i=1;i<t.length();i++)
{
start=ne[start][t[i]-'a'];//位置移动的下一个字符的位置
if(start==-1)
return false;
}
return true;
}
int main()
{
int n;
getline(cin,s);
build();
cin>>n;
while(n--)
{
cin>>t;
if(check())
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}return 0;
}